diff --git a/docs/decisions/0031-kernel-filters.md b/docs/decisions/0031-kernel-filters.md new file mode 100644 index 000000000000..ede793684986 --- /dev/null +++ b/docs/decisions/0031-kernel-filters.md @@ -0,0 +1,150 @@ +--- +# These are optional elements. Feel free to remove any of them. +status: accepted +contact: dmytrostruk +date: 2023-01-23 +deciders: sergeymenshykh, markwallace, rbarreto, stephentoub, dmytrostruk +--- + +# Kernel Filters + +## Context and Problem Statement + +Current way of intercepting some event during function execution works as expected using Kernel Events and event handlers. Example: + +```csharp +ILogger logger = loggerFactory.CreateLogger("MyLogger"); + +var kernel = Kernel.CreateBuilder() + .AddOpenAIChatCompletion( + modelId: TestConfiguration.OpenAI.ChatModelId, + apiKey: TestConfiguration.OpenAI.ApiKey) + .Build(); + +void MyInvokingHandler(object? sender, FunctionInvokingEventArgs e) +{ + logger.LogInformation("Invoking: {FunctionName}", e.Function.Name) +} + +void MyInvokedHandler(object? sender, FunctionInvokedEventArgs e) +{ + if (e.Result.Metadata is not null && e.Result.Metadata.ContainsKey("Usage")) + { + logger.LogInformation("Token usage: {TokenUsage}", e.Result.Metadata?["Usage"]?.AsJson()); + } +} + +kernel.FunctionInvoking += MyInvokingHandler; +kernel.FunctionInvoked += MyInvokedHandler; + +var result = await kernel.InvokePromptAsync("How many days until Christmas? Explain your thinking.") +``` + +There are a couple of problems with this approach: + +1. Event handlers does not support dependency injection. It's hard to get access to specific service, which is registered in application, unless the handler is defined in the same scope where specific service is available. This approach provides some limitations in what place in solution the handler could be defined. (e.g. If developer wants to use `ILoggerFactory` in handler, the handler should be defined in place where `ILoggerFactory` instance is available). +2. It's not clear in what specific period of application runtime the handler should be attached to kernel. Also, it's not clear if developer needs to detach it at some point. +3. Mechanism of events and event handlers in .NET may not be familiar to .NET developers who didn't work with events previously. + + + +## Decision Drivers + +1. Dependency injection for handlers should be supported to easily access registered services within application. +2. There should not be any limitations where handlers are defined within solution, whether it's Startup.cs or separate file. +3. There should be clear way of registering and removing handlers at specific point of application runtime. +4. The mechanism of receiving and processing events in Kernel should be easy and common in .NET ecosystem. +5. New approach should support the same functionality that is available in Kernel Events - cancel function execution, change kernel arguments, change rendered prompt before sending it to AI etc. + +## Decision Outcome + +Introduce Kernel Filters - the approach of receiving the events in Kernel in similar way as action filters in ASP.NET. + +Two new abstractions will be used across Semantic Kernel and developers will have to implement these abstractions in a way that will cover their needs. + +For function-related events: `IFunctionFilter` + +```csharp +public interface IFunctionFilter +{ + void OnFunctionInvoking(FunctionInvokingContext context); + + void OnFunctionInvoked(FunctionInvokedContext context); +} +``` + +For prompt-related events: `IPromptFilter` + +```csharp +public interface IPromptFilter +{ + void OnPromptRendering(PromptRenderingContext context); + + void OnPromptRendered(PromptRenderedContext context); +} +``` + +New approach will allow developers to define filters in separate classes and easily inject required services to process kernel event correctly: + +MyFunctionFilter.cs - filter with the same logic as event handler presented above: + +```csharp +public sealed class MyFunctionFilter : IFunctionFilter +{ + private readonly ILogger _logger; + + public MyFunctionFilter(ILoggerFactory loggerFactory) + { + this._logger = loggerFactory.CreateLogger("MyLogger"); + } + + public void OnFunctionInvoking(FunctionInvokingContext context) + { + this._logger.LogInformation("Invoking {FunctionName}", context.Function.Name); + } + + public void OnFunctionInvoked(FunctionInvokedContext context) + { + var metadata = context.Result.Metadata; + + if (metadata is not null && metadata.ContainsKey("Usage")) + { + this._logger.LogInformation("Token usage: {TokenUsage}", metadata["Usage"]?.AsJson()); + } + } +} +``` + +As soon as new filter is defined, it's easy to configure it to be used in Kernel using dependency injection (pre-construction) or add filter after Kernel initialization (post-construction): + +```csharp +IKernelBuilder kernelBuilder = Kernel.CreateBuilder(); +kernelBuilder.AddOpenAIChatCompletion( + modelId: TestConfiguration.OpenAI.ChatModelId, + apiKey: TestConfiguration.OpenAI.ApiKey); + +// Adding filter with DI (pre-construction) +kernelBuilder.Services.AddSingleton(); + +Kernel kernel = kernelBuilder.Build(); + +// Adding filter after Kernel initialization (post-construction) +// kernel.FunctionFilters.Add(new MyAwesomeFilter()); + +var result = await kernel.InvokePromptAsync("How many days until Christmas? Explain your thinking."); +``` + +It's also possible to configure multiple filters which will be triggered in order of registration: + +```csharp +kernelBuilder.Services.AddSingleton(); +kernelBuilder.Services.AddSingleton(); +kernelBuilder.Services.AddSingleton(); +``` + +And it's possible to change the order of filter execution in runtime or remove specific filter if needed: + +```csharp +kernel.FunctionFilters.Insert(0, new InitialFilter()); +kernel.FunctionFilters.RemoveAt(1); +``` diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props index 0807f280cc33..548452dab4b2 100644 --- a/dotnet/Directory.Packages.props +++ b/dotnet/Directory.Packages.props @@ -51,7 +51,7 @@ - + @@ -59,7 +59,7 @@ - + diff --git a/dotnet/SK-dotnet.sln b/dotnet/SK-dotnet.sln index 3dd0ad6a37a9..d1fb50c21895 100644 --- a/dotnet/SK-dotnet.sln +++ b/dotnet/SK-dotnet.sln @@ -116,6 +116,9 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "System", "System", "{3CDE10B2-AE8F-4FC4-8D55-92D4AD32E144}" ProjectSection(SolutionItems) = preProject src\InternalUtilities\src\System\EnvExtensions.cs = src\InternalUtilities\src\System\EnvExtensions.cs + src\InternalUtilities\src\System\InternalTypeConverter.cs = src\InternalUtilities\src\System\InternalTypeConverter.cs + src\InternalUtilities\src\System\TypeConverterFactory.cs = src\InternalUtilities\src\System\TypeConverterFactory.cs + src\InternalUtilities\src\System\NonNullCollection.cs = src\InternalUtilities\src\System\NonNullCollection.cs EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Type", "Type", "{E85EA4D0-BB7E-4DFD-882F-A76EB8C0B8FF}" diff --git a/dotnet/docs/EXPERIMENTS.md b/dotnet/docs/EXPERIMENTS.md index 3344314246bc..50f99e702de2 100644 --- a/dotnet/docs/EXPERIMENTS.md +++ b/dotnet/docs/EXPERIMENTS.md @@ -15,7 +15,7 @@ You can use the following diagnostic IDs to ignore warnings or errors for a part - SKEXP0001: Embedding services - SKEXP0002: Image services - SKEXP0003: Memory connectors -- SKEXP0004: Kernel Events +- SKEXP0004: Kernel Filters ## OpenAI and Azure OpenAI services diff --git a/dotnet/nuget/nuget-package.props b/dotnet/nuget/nuget-package.props index d070d75d8bc9..67e9322e3b04 100644 --- a/dotnet/nuget/nuget-package.props +++ b/dotnet/nuget/nuget-package.props @@ -1,7 +1,7 @@ - 1.1.0 + 1.2.0 $(VersionPrefix)-$(VersionSuffix) $(VersionPrefix) @@ -10,7 +10,7 @@ true - 1.1.0 + 1.2.0 $(NoWarn);CP0003 diff --git a/dotnet/samples/KernelSyntaxExamples/BaseTest.cs b/dotnet/samples/KernelSyntaxExamples/BaseTest.cs index 8e03d5ab9151..7b187d2687bd 100644 --- a/dotnet/samples/KernelSyntaxExamples/BaseTest.cs +++ b/dotnet/samples/KernelSyntaxExamples/BaseTest.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; using RepoUtils; using Xunit.Abstractions; @@ -10,9 +11,13 @@ public abstract class BaseTest { protected ITestOutputHelper Output { get; } + protected ILoggerFactory LoggerFactory { get; } + protected BaseTest(ITestOutputHelper output) { this.Output = output; + this.LoggerFactory = new XunitLogger(output); + LoadUserSecrets(); } diff --git a/dotnet/samples/KernelSyntaxExamples/Example57_KernelHooks.cs b/dotnet/samples/KernelSyntaxExamples/Example57_KernelHooks.cs index 18b95204e945..d0e33e991d83 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example57_KernelHooks.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example57_KernelHooks.cs @@ -10,6 +10,8 @@ namespace Examples; +#pragma warning disable CS0618 // Events are deprecated + public class Example57_KernelHooks : BaseTest { /// diff --git a/dotnet/samples/KernelSyntaxExamples/Example74_FlowOrchestrator.cs b/dotnet/samples/KernelSyntaxExamples/Example74_FlowOrchestrator.cs index 39a3556be7db..b5a924e72b66 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example74_FlowOrchestrator.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example74_FlowOrchestrator.cs @@ -8,6 +8,7 @@ using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.ChatCompletion; @@ -60,94 +61,12 @@ public class Example74_FlowOrchestrator : BaseTest public Task RunAsync() { return RunExampleAsync(); - //return RunInteractiveAsync(); } - private static async Task RunInteractiveAsync() + private async Task RunExampleAsync() { var bingConnector = new BingConnector(TestConfiguration.Bing.ApiKey); var webSearchEnginePlugin = new WebSearchEnginePlugin(bingConnector); - using var loggerFactory = LoggerFactory.Create(loggerBuilder => - loggerBuilder - .AddConsole() - .AddFilter(null, LogLevel.Error)); - Dictionary plugins = new() - { - { webSearchEnginePlugin, "WebSearch" }, - { new TimePlugin(), "Time" } - }; - - FlowOrchestrator orchestrator = new( - GetKernelBuilder(loggerFactory), - await FlowStatusProvider.ConnectAsync(new VolatileMemoryStore()).ConfigureAwait(false), - plugins, - config: GetOrchestratorConfig()); - var sessionId = Guid.NewGuid().ToString(); - - Console.WriteLine("*****************************************************"); - Console.WriteLine("Executing {0}", nameof(RunInteractiveAsync)); - Stopwatch sw = new(); - sw.Start(); - Console.WriteLine("Flow: " + s_flow.Name); - Console.WriteLine("Please type the question you'd like to ask"); - FunctionResult? result = null; - string? goal = null; - do - { - Console.WriteLine("User: "); - string input = Console.ReadLine() ?? string.Empty; - - if (string.IsNullOrEmpty(input)) - { - Console.WriteLine("No input, exiting"); - break; - } - - if (string.IsNullOrEmpty(goal)) - { - goal = input; - s_flow.Steps.First().Goal = input; - } - - try - { - result = await orchestrator.ExecuteFlowAsync(s_flow, sessionId, input); - } - catch (KernelException ex) - { - Console.WriteLine("Error: " + ex.Message); - Console.WriteLine("Please try again."); - continue; - } - - var responses = result.GetValue>()!; - foreach (var response in responses) - { - Console.WriteLine("Assistant: " + response); - } - - if (result.IsComplete(s_flow)) - { - Console.WriteLine("\tEmail Address: " + result.Metadata!["email_addresses"]); - Console.WriteLine("\tEmail Payload: " + result.Metadata!["email"]); - - Console.WriteLine("Flow completed, exiting"); - break; - } - } while (result == null || result.GetValue>()?.Count > 0); - - Console.WriteLine("Time Taken: " + sw.Elapsed); - Console.WriteLine("*****************************************************"); - } - - private static async Task RunExampleAsync() - { - var bingConnector = new BingConnector(TestConfiguration.Bing.ApiKey); - var webSearchEnginePlugin = new WebSearchEnginePlugin(bingConnector); - using var loggerFactory = LoggerFactory.Create(loggerBuilder => - loggerBuilder - .AddConsole() - .AddFilter(null, LogLevel.Error)); Dictionary plugins = new() { @@ -156,23 +75,23 @@ private static async Task RunExampleAsync() }; FlowOrchestrator orchestrator = new( - GetKernelBuilder(loggerFactory), + GetKernelBuilder(LoggerFactory), await FlowStatusProvider.ConnectAsync(new VolatileMemoryStore()).ConfigureAwait(false), plugins, config: GetOrchestratorConfig()); var sessionId = Guid.NewGuid().ToString(); - Console.WriteLine("*****************************************************"); - Console.WriteLine("Executing {0}", nameof(RunExampleAsync)); + WriteLine("*****************************************************"); + WriteLine("Executing " + nameof(RunExampleAsync)); Stopwatch sw = new(); sw.Start(); - Console.WriteLine("Flow: " + s_flow.Name); + WriteLine("Flow: " + s_flow.Name); var question = s_flow.Steps.First().Goal; var result = await orchestrator.ExecuteFlowAsync(s_flow, sessionId, question).ConfigureAwait(false); - Console.WriteLine("Question: " + question); - Console.WriteLine("Answer: " + result.Metadata!["answer"]); - Console.WriteLine("Assistant: " + result.GetValue>()!.Single()); + WriteLine("Question: " + question); + WriteLine("Answer: " + result.Metadata!["answer"]); + WriteLine("Assistant: " + result.GetValue>()!.Single()); string[] userInputs = new[] { @@ -185,12 +104,12 @@ await FlowStatusProvider.ConnectAsync(new VolatileMemoryStore()).ConfigureAwait( foreach (var t in userInputs) { - Console.WriteLine($"User: {t}"); + WriteLine($"User: {t}"); result = await orchestrator.ExecuteFlowAsync(s_flow, sessionId, t).ConfigureAwait(false); var responses = result.GetValue>()!; foreach (var response in responses) { - Console.WriteLine("Assistant: " + response); + WriteLine("Assistant: " + response); } if (result.IsComplete(s_flow)) @@ -199,11 +118,11 @@ await FlowStatusProvider.ConnectAsync(new VolatileMemoryStore()).ConfigureAwait( } } - Console.WriteLine("\tEmail Address: " + result.Metadata!["email_addresses"]); - Console.WriteLine("\tEmail Payload: " + result.Metadata!["email"]); + WriteLine("\tEmail Address: " + result.Metadata!["email_addresses"]); + WriteLine("\tEmail Payload: " + result.Metadata!["email"]); - Console.WriteLine("Time Taken: " + sw.Elapsed); - Console.WriteLine("*****************************************************"); + WriteLine("Time Taken: " + sw.Elapsed); + WriteLine("*****************************************************"); } private static FlowOrchestratorConfig GetOrchestratorConfig() @@ -219,6 +138,7 @@ private static FlowOrchestratorConfig GetOrchestratorConfig() private static IKernelBuilder GetKernelBuilder(ILoggerFactory loggerFactory) { var builder = Kernel.CreateBuilder(); + builder.Services.AddSingleton(loggerFactory); return builder .AddAzureOpenAIChatCompletion( diff --git a/dotnet/samples/KernelSyntaxExamples/Example77_StronglyTypedFunctionResult.cs b/dotnet/samples/KernelSyntaxExamples/Example77_StronglyTypedFunctionResult.cs new file mode 100644 index 000000000000..cd1a0db181ef --- /dev/null +++ b/dotnet/samples/KernelSyntaxExamples/Example77_StronglyTypedFunctionResult.cs @@ -0,0 +1,148 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Text.Json; +using System.Threading.Tasks; +using Azure.AI.OpenAI; +using Microsoft.SemanticKernel; +using Xunit; +using Xunit.Abstractions; + +namespace Examples; + +// The following example shows how to receive the results from the kernel in a strongly typed object +// which stores the usage in tokens and converts the JSON result to a strongly typed object, where a validation can also +// be performed +public class Example77_StronglyTypedFunctionResult : BaseTest +{ + [Fact] + public async Task RunAsync() + { + this.WriteLine("======== Extended function result ========"); + + Kernel kernel = Kernel.CreateBuilder() + .AddOpenAIChatCompletion( + modelId: TestConfiguration.OpenAI.ChatModelId, + apiKey: TestConfiguration.OpenAI.ApiKey) + .Build(); + + var promptTestDataGeneration = "Return a JSON with an array of 3 JSON objects with the following fields: " + + "First, an id field with a random GUID, next a name field with a random company name and last a description field with a random short company description. " + + "Ensure the JSON is valid and it contains a JSON array named testcompanies with the three fields."; + + // Time it + var sw = new Stopwatch(); + sw.Start(); + + FunctionResult functionResult = await kernel.InvokePromptAsync(promptTestDataGeneration); + + // Stop the timer + sw.Stop(); + + var functionResultTestDataGen = new FunctionResultTestDataGen(functionResult!, sw.ElapsedMilliseconds); + + this.WriteLine($"Test data: {functionResultTestDataGen.Result} \n"); + this.WriteLine($"Milliseconds: {functionResultTestDataGen.ExecutionTimeInMilliseconds} \n"); + this.WriteLine($"Total Tokens: {functionResultTestDataGen.TokenCounts!.TotalTokens} \n"); + } + + public Example77_StronglyTypedFunctionResult(ITestOutputHelper output) : base(output) + { + } + + /// + /// Helper classes for the example, + /// put in the same file for simplicity + /// + /// The structure to put the JSON result in a strongly typed object + private sealed class RootObject + { + public List TestCompanies { get; set; } + } + + private sealed class TestCompany + { + public string Id { get; set; } + public string Name { get; set; } + public string Description { get; set; } + } + + /// + /// The FunctionResult custom wrapper to parse the result and the tokens + /// + private sealed class FunctionResultTestDataGen : FunctionResultExtended + { + public List TestCompanies { get; set; } + + public long ExecutionTimeInMilliseconds { get; init; } + + public FunctionResultTestDataGen(FunctionResult functionResult, long executionTimeInMilliseconds) + : base(functionResult) + { + this.TestCompanies = ParseTestCompanies(); + this.ExecutionTimeInMilliseconds = executionTimeInMilliseconds; + this.TokenCounts = this.ParseTokenCounts(); + } + + private TokenCounts? ParseTokenCounts() + { + CompletionsUsage? usage = FunctionResult.Metadata?["Usage"] as CompletionsUsage; + + return new TokenCounts( + completionTokens: usage?.CompletionTokens ?? 0, + promptTokens: usage?.PromptTokens ?? 0, + totalTokens: usage?.TotalTokens ?? 0); + } + + private static readonly JsonSerializerOptions s_jsonSerializerOptions = new() + { + PropertyNameCaseInsensitive = true + }; + + private List ParseTestCompanies() + { + // This could also perform some validation logic + var rootObject = JsonSerializer.Deserialize(this.Result, s_jsonSerializerOptions); + List companies = rootObject!.TestCompanies; + + return companies; + } + } + + private sealed class TokenCounts + { + public int CompletionTokens { get; init; } + public int PromptTokens { get; init; } + public int TotalTokens { get; init; } + + public TokenCounts(int completionTokens, int promptTokens, int totalTokens) + { + CompletionTokens = completionTokens; + PromptTokens = promptTokens; + TotalTokens = totalTokens; + } + } + + /// + /// The FunctionResult extension to provide base functionality + /// + private class FunctionResultExtended + { + public string Result { get; init; } + public TokenCounts? TokenCounts { get; set; } + + public FunctionResult FunctionResult { get; init; } + + public FunctionResultExtended(FunctionResult functionResult) + { + this.FunctionResult = functionResult; + this.Result = this.ParseResultFromFunctionResult(); + } + + private string ParseResultFromFunctionResult() + { + return this.FunctionResult.GetValue() ?? string.Empty; + } + } +} diff --git a/dotnet/samples/KernelSyntaxExamples/Getting_Started/Step6_Responsible_AI.cs b/dotnet/samples/KernelSyntaxExamples/Getting_Started/Step6_Responsible_AI.cs index 8c80ad9014fc..ac68ac77a580 100644 --- a/dotnet/samples/KernelSyntaxExamples/Getting_Started/Step6_Responsible_AI.cs +++ b/dotnet/samples/KernelSyntaxExamples/Getting_Started/Step6_Responsible_AI.cs @@ -2,54 +2,73 @@ using System.Threading.Tasks; using Examples; +using Microsoft.Extensions.DependencyInjection; using Microsoft.SemanticKernel; using Xunit; using Xunit.Abstractions; namespace GettingStarted; -// This example shows how to use rendering event hooks to ensure that prompts are rendered in a responsible manner. public class Step6_Responsible_AI : BaseTest { /// - /// Show how to use rendering event hooks to ensure that prompts are rendered in a responsible manner. + /// Show how to use prompt filters to ensure that prompts are rendered in a responsible manner. /// [Fact] public async Task RunAsync() { // Create a kernel with OpenAI chat completion - Kernel kernel = Kernel.CreateBuilder() + var builder = Kernel.CreateBuilder() .AddOpenAIChatCompletion( modelId: TestConfiguration.OpenAI.ChatModelId, - apiKey: TestConfiguration.OpenAI.ApiKey) - .Build(); + apiKey: TestConfiguration.OpenAI.ApiKey); - // Handler which is called before a prompt is rendered - void MyRenderingHandler(object? sender, PromptRenderingEventArgs e) - { - if (e.Arguments.ContainsName("card_number")) - { - e.Arguments["card_number"] = "**** **** **** ****"; - } - } - - // Handler which is called after a prompt is rendered - void MyRenderedHandler(object? sender, PromptRenderedEventArgs e) - { - e.RenderedPrompt += " NO SEXISM, RACISM OR OTHER BIAS/BIGOTRY"; + builder.Services.AddSingleton(this.Output); - WriteLine(e.RenderedPrompt); - } + // Add prompt filter to the kernel + builder.Services.AddSingleton(); - // Add the handlers to the kernel - kernel.PromptRendering += MyRenderingHandler; - kernel.PromptRendered += MyRenderedHandler; + var kernel = builder.Build(); KernelArguments arguments = new() { { "card_number", "4444 3333 2222 1111" } }; - WriteLine(await kernel.InvokePromptAsync("Tell me some useful information about this credit card number {{$card_number}}?", arguments)); + + var result = await kernel.InvokePromptAsync("Tell me some useful information about this credit card number {{$card_number}}?", arguments); + + WriteLine(result); } public Step6_Responsible_AI(ITestOutputHelper output) : base(output) { } + + private sealed class PromptFilter : IPromptFilter + { + private readonly ITestOutputHelper _output; + + public PromptFilter(ITestOutputHelper output) + { + this._output = output; + } + + /// + /// Method which is called before a prompt is rendered. + /// + public void OnPromptRendered(PromptRenderedContext context) + { + context.RenderedPrompt += " NO SEXISM, RACISM OR OTHER BIAS/BIGOTRY"; + + this._output.WriteLine(context.RenderedPrompt); + } + + /// + /// Method which is called after a prompt is rendered. + /// + public void OnPromptRendering(PromptRenderingContext context) + { + if (context.Arguments.ContainsName("card_number")) + { + context.Arguments["card_number"] = "**** **** **** ****"; + } + } + } } diff --git a/dotnet/samples/KernelSyntaxExamples/Getting_Started/Step7_Observability.cs b/dotnet/samples/KernelSyntaxExamples/Getting_Started/Step7_Observability.cs index 464e7ca80b1c..33e0b6fba298 100644 --- a/dotnet/samples/KernelSyntaxExamples/Getting_Started/Step7_Observability.cs +++ b/dotnet/samples/KernelSyntaxExamples/Getting_Started/Step7_Observability.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using System.Threading.Tasks; using Examples; +using Microsoft.Extensions.DependencyInjection; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Connectors.OpenAI; using RepoUtils; @@ -15,17 +16,48 @@ namespace GettingStarted; public class Step7_Observability : BaseTest { /// - /// Shows different ways observe the execution of a instances. + /// Shows how to observe the execution of a instance with filters. /// [Fact] - public async Task RunAsync() + public async Task ObservabilityWithFiltersAsync() { // Create a kernel with OpenAI chat completion IKernelBuilder kernelBuilder = Kernel.CreateBuilder(); kernelBuilder.AddOpenAIChatCompletion( modelId: TestConfiguration.OpenAI.ChatModelId, apiKey: TestConfiguration.OpenAI.ApiKey); + kernelBuilder.Plugins.AddFromType(); + + // Add filter using DI + kernelBuilder.Services.AddSingleton(this.Output); + kernelBuilder.Services.AddSingleton(); + + Kernel kernel = kernelBuilder.Build(); + + // Add filter without DI + kernel.PromptFilters.Add(new MyPromptFilter(this.Output)); + + // Invoke the kernel with a prompt and allow the AI to automatically invoke functions + OpenAIPromptExecutionSettings settings = new() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }; + WriteLine(await kernel.InvokePromptAsync("How many days until Christmas? Explain your thinking.", new(settings))); + } + + /// + /// Shows how to observe the execution of a instance with hooks. + /// + [Fact] + [Obsolete("Events are deprecated in favor of filters.")] + public async Task ObservabilityWithHooksAsync() + { + // Create a kernel with OpenAI chat completion + IKernelBuilder kernelBuilder = Kernel.CreateBuilder(); + kernelBuilder.AddOpenAIChatCompletion( + modelId: TestConfiguration.OpenAI.ChatModelId, + apiKey: TestConfiguration.OpenAI.ApiKey); + + kernelBuilder.Plugins.AddFromType(); + Kernel kernel = kernelBuilder.Build(); // Handler which is called before a function is invoked @@ -34,6 +66,12 @@ void MyInvokingHandler(object? sender, FunctionInvokingEventArgs e) WriteLine($"Invoking {e.Function.Name}"); } + // Handler which is called before a prompt is rendered + void MyRenderingHandler(object? sender, PromptRenderingEventArgs e) + { + WriteLine($"Rendering prompt for {e.Function.Name}"); + } + // Handler which is called after a prompt is rendered void MyRenderedHandler(object? sender, PromptRenderedEventArgs e) { @@ -51,6 +89,7 @@ void MyInvokedHandler(object? sender, FunctionInvokedEventArgs e) // Add the handlers to the kernel kernel.FunctionInvoking += MyInvokingHandler; + kernel.PromptRendering += MyRenderingHandler; kernel.PromptRendered += MyRenderedHandler; kernel.FunctionInvoked += MyInvokedHandler; @@ -62,13 +101,64 @@ void MyInvokedHandler(object? sender, FunctionInvokedEventArgs e) /// /// A plugin that returns the current time. /// - public class TimeInformation + private sealed class TimeInformation { [KernelFunction] [Description("Retrieves the current time in UTC.")] public string GetCurrentUtcTime() => DateTime.UtcNow.ToString("R"); } + /// + /// Function filter for observability. + /// + private sealed class MyFunctionFilter : IFunctionFilter + { + private readonly ITestOutputHelper _output; + + public MyFunctionFilter(ITestOutputHelper output) + { + this._output = output; + } + + public void OnFunctionInvoked(FunctionInvokedContext context) + { + var metadata = context.Result.Metadata; + + if (metadata is not null && metadata.ContainsKey("Usage")) + { + this._output.WriteLine($"Token usage: {metadata["Usage"]?.AsJson()}"); + } + } + + public void OnFunctionInvoking(FunctionInvokingContext context) + { + this._output.WriteLine($"Invoking {context.Function.Name}"); + } + } + + /// + /// Prompt filter for observability. + /// + private sealed class MyPromptFilter : IPromptFilter + { + private readonly ITestOutputHelper _output; + + public MyPromptFilter(ITestOutputHelper output) + { + this._output = output; + } + + public void OnPromptRendered(PromptRenderedContext context) + { + this._output.WriteLine($"Prompt sent to model: {context.RenderedPrompt}"); + } + + public void OnPromptRendering(PromptRenderingContext context) + { + this._output.WriteLine($"Rendering prompt for {context.Function.Name}"); + } + } + public Step7_Observability(ITestOutputHelper output) : base(output) { } diff --git a/dotnet/samples/KernelSyntaxExamples/RepoUtils/XunitLogger.cs b/dotnet/samples/KernelSyntaxExamples/RepoUtils/XunitLogger.cs new file mode 100644 index 000000000000..1342a7fae834 --- /dev/null +++ b/dotnet/samples/KernelSyntaxExamples/RepoUtils/XunitLogger.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using Microsoft.Extensions.Logging; +using Xunit.Abstractions; + +namespace RepoUtils; + +/// +/// A logger that writes to the Xunit test output +/// +internal sealed class XunitLogger : ILoggerFactory, ILogger, IDisposable +{ + private readonly ITestOutputHelper _output; + + public XunitLogger(ITestOutputHelper output) + { + this._output = output; + } + + /// + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + this._output.WriteLine(state?.ToString()); + } + + /// + public bool IsEnabled(LogLevel logLevel) => true; + + /// + public IDisposable BeginScope(TState state) where TState : notnull + => this; + + /// + public void Dispose() + { + // This class is marked as disposable to support the BeginScope method. + // However, there is no need to dispose anything. + } + + public ILogger CreateLogger(string categoryName) => this; + + public void AddProvider(ILoggerProvider provider) => throw new NotSupportedException(); +} diff --git a/dotnet/src/Experimental/Orchestration.Flow/Execution/FlowExecutor.cs b/dotnet/src/Experimental/Orchestration.Flow/Execution/FlowExecutor.cs index f719c7543435..a53fac6c5d97 100644 --- a/dotnet/src/Experimental/Orchestration.Flow/Execution/FlowExecutor.cs +++ b/dotnet/src/Experimental/Orchestration.Flow/Execution/FlowExecutor.cs @@ -117,12 +117,10 @@ internal FlowExecutor(IKernelBuilder kernelBuilder, IFlowStatusProvider statusPr this._flowStatusProvider = statusProvider; this._globalPluginCollection = globalPluginCollection; - var checkRepeatStepConfig = PromptTemplateConfig.FromJson(EmbeddedResource.Read("Plugins.CheckRepeatStep.config.json")!); - checkRepeatStepConfig.Template = EmbeddedResource.Read("Plugins.CheckRepeatStep.skprompt.txt")!; + var checkRepeatStepConfig = this.ImportPromptTemplateConfig("CheckRepeatStep"); this._checkRepeatStepFunction = KernelFunctionFactory.CreateFromPrompt(checkRepeatStepConfig); - var checkStartStepConfig = PromptTemplateConfig.FromJson(EmbeddedResource.Read("Plugins.CheckStartStep.config.json")!); - checkStartStepConfig.Template = EmbeddedResource.Read("Plugins.CheckStartStep.skprompt.txt")!; + var checkStartStepConfig = this.ImportPromptTemplateConfig("CheckStartStep"); this._checkStartStepFunction = KernelFunctionFactory.CreateFromPrompt(checkStartStepConfig); this._config.ExcludedPlugins.Add(RestrictedPluginName); @@ -132,6 +130,23 @@ internal FlowExecutor(IKernelBuilder kernelBuilder, IFlowStatusProvider statusPr this._executeStepFunction = KernelFunctionFactory.CreateFromMethod(this.ExecuteStepAsync, "ExecuteStep", "Execute a flow step"); } + private PromptTemplateConfig ImportPromptTemplateConfig(string functionName) + { + var config = KernelFunctionYaml.ToPromptTemplateConfig(EmbeddedResource.Read($"Plugins.{functionName}.yaml")!); + + // if AIServiceIds is specified, only include the relevant execution settings + if (this._config.AIServiceIds.Count > 0) + { + var serviceIdsToRemove = config.ExecutionSettings.Keys.Except(this._config.AIServiceIds); + foreach (var serviceId in serviceIdsToRemove) + { + config.ExecutionSettings.Remove(serviceId); + } + } + + return config; + } + public async Task ExecuteFlowAsync(Flow flow, string sessionId, string input, KernelArguments kernelArguments) { Verify.NotNull(flow, nameof(flow)); @@ -262,12 +277,11 @@ public async Task ExecuteFlowAsync(Flow flow, string sessionId, if (!string.IsNullOrEmpty(stepResult.ToString()) && (stepResult.IsPromptInput() || stepResult.IsTerminateFlow())) { - try + if (stepResult.ValueType == typeof(List)) { - var stepOutputs = JsonSerializer.Deserialize(stepResult.ToString()); - outputs.AddRange(stepOutputs!); + outputs.AddRange(stepResult.GetValue>()!); } - catch (JsonException) + else { outputs.Add(stepResult.ToString()); } @@ -276,9 +290,15 @@ public async Task ExecuteFlowAsync(Flow flow, string sessionId, { stepState.Status = ExecutionState.Status.Completed; - var metadata = stepResult.Metadata! - .Where(kvp => step.Provides.Contains(kvp.Key)) - .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + var metadata = stepResult.Metadata!.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + foreach (var variable in step.Provides) + { + if (!metadata.ContainsKey(variable)) + { + metadata[variable] = string.Empty; + } + } + stepResult = new FunctionResult(stepResult.Function, stepResult.GetValue(), metadata: metadata); if (!string.IsNullOrWhiteSpace(exitResponse)) @@ -538,7 +558,7 @@ private void ValidateStep(FlowStep step, KernelArguments context) } this._logger.LogWarning("Missing result tag from {Function} : {ActionText}", "CheckRepeatOrStartStep", llmResponseText); - chatHistory.AddSystemMessage(llmResponseText + "\nI should provide either [QUESTION] or [FINAL_ANSWER]"); + chatHistory.AddSystemMessage(llmResponseText + "\nI should provide either [QUESTION] or [FINAL_ANSWER]."); await this._flowStatusProvider.SaveChatHistoryAsync(sessionId, checkRepeatOrStartStepId, chatHistory).ConfigureAwait(false); return null; } @@ -603,7 +623,15 @@ private async Task ExecuteStepAsync(FlowStep step, string sessio this._logger.LogInformation("Thought: {Thought}", actionStep.Thought); } - if (!string.IsNullOrEmpty(actionStep.Action!)) + if (!string.IsNullOrEmpty(actionStep.FinalAnswer)) + { + if (step.Provides.Count() == 1) + { + arguments[step.Provides.Single()] = actionStep.FinalAnswer; + return new FunctionResult(this._executeStepFunction, actionStep.FinalAnswer, metadata: arguments); + } + } + else if (!string.IsNullOrEmpty(actionStep.Action!)) { if (actionStep.Action!.Contains(Constants.StopAndPromptFunctionName)) { @@ -727,14 +755,6 @@ private async Task ExecuteStepAsync(FlowStep step, string sessio this._logger?.LogWarning("Action: No result from action"); } - else if (!string.IsNullOrEmpty(actionStep.FinalAnswer)) - { - if (step.Provides.Count() == 1) - { - arguments[step.Provides.Single()] = actionStep.FinalAnswer; - return new FunctionResult(this._executeStepFunction, actionStep.FinalAnswer, metadata: arguments); - } - } else { actionStep.Observation = "ACTION $JSON_BLOB must be provided as part of thought process."; diff --git a/dotnet/src/Experimental/Orchestration.Flow/Execution/ReActEngine.cs b/dotnet/src/Experimental/Orchestration.Flow/Execution/ReActEngine.cs index cab59dfbe4aa..6409ab0144d1 100644 --- a/dotnet/src/Experimental/Orchestration.Flow/Execution/ReActEngine.cs +++ b/dotnet/src/Experimental/Orchestration.Flow/Execution/ReActEngine.cs @@ -93,34 +93,19 @@ internal ReActEngine(Kernel systemKernel, ILogger logger, FlowOrchestratorConfig var promptConfig = config.ReActPromptTemplateConfig; if (promptConfig is null) { - promptConfig = new PromptTemplateConfig(); - - string promptConfigString = EmbeddedResource.Read("Plugins.ReActEngine.config.json")!; + string promptConfigString = EmbeddedResource.Read("Plugins.ReActEngine.yaml")!; if (!string.IsNullOrEmpty(modelId)) { - var modelConfigString = EmbeddedResource.Read($"Plugins.ReActEngine.{modelId}.config.json", false); + var modelConfigString = EmbeddedResource.Read($"Plugins.ReActEngine.{modelId}.yaml", false); promptConfigString = string.IsNullOrEmpty(modelConfigString) ? promptConfigString : modelConfigString!; } - if (!string.IsNullOrEmpty(promptConfigString)) - { - promptConfig = PromptTemplateConfig.FromJson(promptConfigString); - } - else - { - promptConfig.SetMaxTokens(config.MaxTokens); - } - } - - var promptTemplate = config.ReActPromptTemplate; - if (string.IsNullOrEmpty(promptTemplate)) - { - promptTemplate = EmbeddedResource.Read("Plugins.ReActEngine.skprompt.txt")!; + promptConfig = KernelFunctionYaml.ToPromptTemplateConfig(promptConfigString); - if (!string.IsNullOrEmpty(promptTemplate)) + if (!string.IsNullOrEmpty(modelId)) { - var modelPromptTemplate = EmbeddedResource.Read($"Plugins.ReActEngine.{modelId}.skprompt.txt", false); - promptConfig.Template = string.IsNullOrEmpty(modelPromptTemplate) ? promptTemplate : modelPromptTemplate!; + var modelConfigString = EmbeddedResource.Read($"Plugins.ReActEngine.{modelId}.yaml", false); + promptConfigString = string.IsNullOrEmpty(modelConfigString) ? promptConfigString : modelConfigString!; } } @@ -174,11 +159,12 @@ internal ReActEngine(Kernel systemKernel, ILogger logger, FlowOrchestratorConfig var actionStep = this.ParseResult(llmResponseText); - if (!string.IsNullOrEmpty(actionStep.Action) || previousSteps.Count == 0) + if (!string.IsNullOrEmpty(actionStep.Action) || previousSteps.Count == 0 || !string.IsNullOrEmpty(actionStep.FinalAnswer)) { return actionStep; } + actionStep.Thought = llmResponseText; actionStep.Observation = "Failed to parse valid action step, missing action or final answer."; this._logger?.LogWarning("Failed to parse valid action step from llm response={LLMResponseText}", llmResponseText); this._logger?.LogWarning("Scratchpad={ScratchPad}", scratchPad); diff --git a/dotnet/src/Experimental/Orchestration.Flow/Experimental.Orchestration.Flow.csproj b/dotnet/src/Experimental/Orchestration.Flow/Experimental.Orchestration.Flow.csproj index 6ab88b788f3d..69d38b2ec362 100644 --- a/dotnet/src/Experimental/Orchestration.Flow/Experimental.Orchestration.Flow.csproj +++ b/dotnet/src/Experimental/Orchestration.Flow/Experimental.Orchestration.Flow.csproj @@ -23,9 +23,7 @@ - - - + diff --git a/dotnet/src/Experimental/Orchestration.Flow/FlowOrchestratorConfig.cs b/dotnet/src/Experimental/Orchestration.Flow/FlowOrchestratorConfig.cs index 34943decdcad..171756034cce 100644 --- a/dotnet/src/Experimental/Orchestration.Flow/FlowOrchestratorConfig.cs +++ b/dotnet/src/Experimental/Orchestration.Flow/FlowOrchestratorConfig.cs @@ -46,11 +46,6 @@ public sealed class FlowOrchestratorConfig /// public int MinIterationTimeMs { get; set; } = 0; - /// - /// Optional. The prompt template override for ReAct engine. - /// - public string? ReActPromptTemplate { get; set; } = null; - /// /// Optional. The prompt template configuration override for the ReAct engine. /// @@ -61,12 +56,17 @@ public sealed class FlowOrchestratorConfig /// public bool EnableAutoTermination { get; set; } = false; + /// + /// Optional. The allowed AI service id for the React engine. + /// + public HashSet AIServiceIds { get; set; } = new(); + /// /// Optional. The AI request settings for the ReAct engine. /// /// /// Prompt used for reasoning may be different for different models, the prompt selection would be based on the PromptExecutionSettings. - /// if the built in prompt template does not work for your model, suggest to override it with . + /// if the built in prompt template does not work for your model, suggest to override it with . /// public PromptExecutionSettings? AIRequestSettings { get; set; } = null; } diff --git a/dotnet/src/Experimental/Orchestration.Flow/Plugins/CheckRepeatStep.yaml b/dotnet/src/Experimental/Orchestration.Flow/Plugins/CheckRepeatStep.yaml new file mode 100644 index 000000000000..0ef31a45f860 --- /dev/null +++ b/dotnet/src/Experimental/Orchestration.Flow/Plugins/CheckRepeatStep.yaml @@ -0,0 +1,69 @@ +template_format: semantic-kernel +template: | + [INSTRUCTION] + Work with user to determine if he or she would like to work on the previous step for one more time. + + [THOUGHT PROCESS] + [Goal] + The goal of proposed step. + [THOUGHT] + To solve this problem, I should carefully analyze the previous question and response to identify if user is willing to repeat the action again. + Any facts I discover earlier in my thought process should be repeated here to keep them readily available. + [QUESTION] + If there is any ambiguity in the chat history, ask the follow up question to the user to get clarification on whether the user wants to repeat the previous step. + The way you will check if the user wants to repeat the step is by asking the question "{{$transitionMessage}}". + IMPORTANT: Do NOT update the wording in the question stated above. If you need clarification, you will ask that question word for word. + If the user says something along the lines of "yes", "sure", "fine", "ok", then they are asking to repeat the step. + [RESPONSE] + The result of the action will be provided here. It could be result or error message of the action, or chat history between assistant and user to tackle the problem. + ... (These THOUGHT/QUESTION/RESPONSE can repeat until the final answer is reached.) + [FINAL ANSWER] + Once I have gathered all the necessary observations and can reliably tell if user would like to repeat the step for one more time, output TRUE or FALSE. + [END THOUGHT PROCESS] + + Example: + [Goal] + {{$goal}} + [QUESTION] + {{$transitionMessage}} + [RESPONSE] + yes + [THOUGHT] + Based on the response, the user wants to try the previous step again. + [FINAL ANSWER] + TRUE + + IMPORTANT REMINDER: Your each response MUST contain one of [QUESTION] and [FINAL ANSWER]! + Let's break down the problem step by step and think about the best approach. + + Begin! + + [Goal] + {{$goal}} + {{$agentScratchPad}} + [THOUGHT] +description: Given the chat history, determine if user would like to execute the previous task for one more time. If not concluded, generate the next message for follow up. +name: CheckRepeatStep +input_variables: + - name: goal + description: The goal of proposed step + - name: transitionMessage + description: The transition message + default: Do you want to try the previous step again? + - name: agentScratchPad + description: The agent's scratch pad +execution_settings: + text-davinci-003: + temperature: 0.0 + top_p: 1.0 + presence_penalty: 0.0 + frequency_penalty: 0.0 + max_tokens: 400 + stop_sequences: ["[RESPONSE]"] + default: + temperature: 0.0 + top_p: 1.0 + presence_penalty: 0.0 + frequency_penalty: 0.0 + max_tokens: 400 + stop_sequences: ["[RESPONSE]"] diff --git a/dotnet/src/Experimental/Orchestration.Flow/Plugins/CheckRepeatStep/config.json b/dotnet/src/Experimental/Orchestration.Flow/Plugins/CheckRepeatStep/config.json deleted file mode 100644 index 03f0790bf3ab..000000000000 --- a/dotnet/src/Experimental/Orchestration.Flow/Plugins/CheckRepeatStep/config.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "schema": 1, - "name": "CheckRepeatStep", - "description": "Given the chat history, determine if user would like to execute the previous task for one more time. If not concluded, generate the next message for follow up.", - "execution_settings": { - "default": { - "max_tokens": 400, - "temperature": 0, - "top_p": 1, - "presence_penalty": 0.0, - "frequency_penalty": 0.0, - "stop_sequences": ["[RESPONSE]"] - } - }, - "input_variables": [ - { - "name": "goal", - "description": "The goal of proposed step", - "default": "" - }, - { - "name": "transitionMessage", - "description": "The transition message", - "default": "Did you want to try the previous step again?" - }, - { - "name": "agentScratchPad", - "description": "The agent's scratch pad", - "default": "" - } - ] -} diff --git a/dotnet/src/Experimental/Orchestration.Flow/Plugins/CheckRepeatStep/skprompt.txt b/dotnet/src/Experimental/Orchestration.Flow/Plugins/CheckRepeatStep/skprompt.txt deleted file mode 100644 index e293d8813609..000000000000 --- a/dotnet/src/Experimental/Orchestration.Flow/Plugins/CheckRepeatStep/skprompt.txt +++ /dev/null @@ -1,42 +0,0 @@ -[INSTRUCTION] -Work with user to determine if he or she would like to work on the previous step for one more time. - -[THOUGHT PROCESS] -[Goal] -The goal of proposed step. -[THOUGHT] -To solve this problem, I should carefully analyze the previous question and response to identify if user is willing to repeat the action again. -Any facts I discover earlier in my thought process should be repeated here to keep them readily available. -[QUESTION] -If there is any ambiguity in the chat history, ask the follow up question to the user to get clarification on whether the user wants to repeat the previous step. -The way you will check if the user wants to repeat the step is by asking the question "{{$transitionMessage}}". -IMPORTANT: Do NOT update the wording in the question stated above. If you need clarification, you will ask that question word for word. -If the user says something along the lines of "yes", "sure", "fine", "ok", then they are asking to repeat the step. -[RESPONSE] -The result of the action will be provided here. It could be result or error message of the action, or chat history between assistant and user to tackle the problem. -... (These THOUGHT/QUESTION/RESPONSE can repeat until the final answer is reached.) -[FINAL ANSWER] -Once I have gathered all the necessary observations and can reliably tell if user would like to repeat the step for one more time, output TRUE or FALSE. -[END THOUGHT PROCESS] - -Example: -[Goal] -{{$goal}} -[QUESTION] -{{$transitionMessage}} -[RESPONSE] -yes -[THOUGHT] -Based on the response, the user wants to try the previous step again. -[FINAL ANSWER] -TRUE - -IMPORTANT REMINDER: Your each response MUST contain one of [QUESTION] and [FINAL ANSWER]! -Let's break down the problem step by step and think about the best approach. - -Begin! - -[Goal] -{{$goal}} -{{$agentScratchPad}} -[THOUGHT] \ No newline at end of file diff --git a/dotnet/src/Experimental/Orchestration.Flow/Plugins/CheckStartStep.yaml b/dotnet/src/Experimental/Orchestration.Flow/Plugins/CheckStartStep.yaml new file mode 100644 index 000000000000..960cbfd898ea --- /dev/null +++ b/dotnet/src/Experimental/Orchestration.Flow/Plugins/CheckStartStep.yaml @@ -0,0 +1,68 @@ +template_format: semantic-kernel +template: | + [INSTRUCTION] + Work with user to determine if he or she would like to execute the current step for the first time. + + [THOUGHT PROCESS] + [Goal] + The goal of proposed step. + [THOUGHT] + To solve this problem, I should carefully analyze the question and response to identify if user is willing to begin the current action. + Any facts I discover earlier in my thought process should be repeated here to keep them readily available. + [QUESTION] + If there is any ambiguity in the chat history, ask the follow up question to the user to get clarification on whether the user wants to repeat the previous step. + The way you will check if the user wants to execute the step is by asking the question "{{$message}}". + IMPORTANT: Do NOT update the wording in the question stated above. If you need clarification, you will ask that question word for word. + If the user says something along the lines of "yes", "sure", "fine", "ok", then they are asking to start the step. + [RESPONSE] + The result of the action will be provided here. It could be result or error message of the action, or chat history between assistant and user to tackle the problem. + ... (These THOUGHT/QUESTION/RESPONSE can repeat until the final answer is reached.) + [FINAL ANSWER] + Once I have gathered all the necessary observations and can reliably tell if user would like to repeat the step for one more time, output TRUE or FALSE. + [END THOUGHT PROCESS] + + Example: + [Goal] + {{$goal}} + [QUESTION] + {{$message}} + [RESPONSE] + yes + [THOUGHT] + Based on the response, the user wants to execute the current step. + [FINAL ANSWER] + TRUE + + IMPORTANT REMINDER: your each response should contain at most one question. Do not provide more than one step. + Let's break down the problem step by step and think about the best approach. + + Begin! + + [Goal] + {{$goal}} + {{$agentScratchPad}} + [THOUGHT] +description: Given the chat history, determine if user would like to execute the previous task for one more time. If not concluded, generate the next message for follow up. +name: CheckRepeatStep +input_variables: + - name: goal + description: The goal of proposed step + - name: message + description: he message to display to the user + - name: agentScratchPad + description: The agent's scratch pad +execution_settings: + text-davinci-003: + temperature: 0.0 + top_p: 1.0 + presence_penalty: 0.0 + frequency_penalty: 0.0 + max_tokens: 400 + stop_sequences: ["[RESPONSE]"] + default: + temperature: 0.0 + top_p: 1.0 + presence_penalty: 0.0 + frequency_penalty: 0.0 + max_tokens: 400 + stop_sequences: ["[RESPONSE]"] diff --git a/dotnet/src/Experimental/Orchestration.Flow/Plugins/CheckStartStep/config.json b/dotnet/src/Experimental/Orchestration.Flow/Plugins/CheckStartStep/config.json deleted file mode 100644 index b4abbfd9f8ed..000000000000 --- a/dotnet/src/Experimental/Orchestration.Flow/Plugins/CheckStartStep/config.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "schema": 1, - "name": "CheckStartStep", - "description": "Given the chat history, determine if user would like to begin executing the current task. If not concluded, generate the next message for follow up.", - "execution_settings": { - "default": { - "max_tokens": 400, - "temperature": 0, - "top_p": 1, - "presence_penalty": 0.0, - "frequency_penalty": 0.0, - "stop_sequences": ["[RESPONSE]"] - } - }, - "input_variables": [ - { - "name": "goal", - "description": "The goal of proposed step", - "default": "" - }, - { - "name": "message", - "description": "The message to display to the user", - "default": "" - }, - { - "name": "agentScratchPad", - "description": "The agent's scratch pad", - "default": "" - } - ] -} diff --git a/dotnet/src/Experimental/Orchestration.Flow/Plugins/CheckStartStep/skprompt.txt b/dotnet/src/Experimental/Orchestration.Flow/Plugins/CheckStartStep/skprompt.txt deleted file mode 100644 index fa95c8b96262..000000000000 --- a/dotnet/src/Experimental/Orchestration.Flow/Plugins/CheckStartStep/skprompt.txt +++ /dev/null @@ -1,42 +0,0 @@ -[INSTRUCTION] -Work with user to determine if he or she would like to execute the current step for the first time. - -[THOUGHT PROCESS] -[Goal] -The goal of proposed step. -[THOUGHT] -To solve this problem, I should carefully analyze the question and response to identify if user is willing to begin the current action. -Any facts I discover earlier in my thought process should be repeated here to keep them readily available. -[QUESTION] -If there is any ambiguity in the chat history, ask the follow up question to the user to get clarification on whether the user wants to repeat the previous step. -The way you will check if the user wants to execute the step is by asking the question "{{$message}}". -IMPORTANT: Do NOT update the wording in the question stated above. If you need clarification, you will ask that question word for word. -If the user says something along the lines of "yes", "sure", "fine", "ok", then they are asking to start the step. -[RESPONSE] -The result of the action will be provided here. It could be result or error message of the action, or chat history between assistant and user to tackle the problem. -... (These THOUGHT/QUESTION/RESPONSE can repeat until the final answer is reached.) -[FINAL ANSWER] -Once I have gathered all the necessary observations and can reliably tell if user would like to repeat the step for one more time, output TRUE or FALSE. -[END THOUGHT PROCESS] - -Example: -[Goal] -{{$goal}} -[QUESTION] -{{$message}} -[RESPONSE] -yes -[THOUGHT] -Based on the response, the user wants to execute the current step. -[FINAL ANSWER] -TRUE - -IMPORTANT REMINDER: your each response should contain at most one question. Do not provide more than one step. -Let's break down the problem step by step and think about the best approach. - -Begin! - -[Goal] -{{$goal}} -{{$agentScratchPad}} -[THOUGHT] \ No newline at end of file diff --git a/dotnet/src/Experimental/Orchestration.Flow/Plugins/ReActEngine.gpt4.yaml b/dotnet/src/Experimental/Orchestration.Flow/Plugins/ReActEngine.gpt4.yaml new file mode 100644 index 000000000000..26e3b1d777cf --- /dev/null +++ b/dotnet/src/Experimental/Orchestration.Flow/Plugins/ReActEngine.gpt4.yaml @@ -0,0 +1,81 @@ +template_format: semantic-kernel +template: | + [INSTRUCTION] + Answer the following questions as accurately as possible using the provided functions. + + [AVAILABLE FUNCTIONS] + The function definitions below are in the following format: + : + - : + - ... + + {{$functionDescriptions}} + [END AVAILABLE FUNCTIONS] + + [USAGE INSTRUCTIONS] + To use the functions, specify a JSON blob representing an action. The JSON blob should contain fully qualified name of the function to use, and an "action_variables" key with a JSON object of string values to use when calling the function. + Do not call functions directly; they must be invoked through an action. + The "action_variables" values should match the defined [PARAMETERS] of the named "action" in [AVAILABLE FUNCTIONS]. + Dictionary values in "action_variables" must be strings and represent the actual values to be passed to the function. + Ensure that the $JSON_BLOB contains only a SINGLE action; do NOT return multiple actions. + IMPORTANT: Use only the available functions listed in the [AVAILABLE FUNCTIONS] section. Do not attempt to use any other functions that are not specified. + The value of parameters should either by empty if the expectation is for the user to provide them and have not been provided yet, or derived from the agent scratchpad. + You are not allowed to ask user directly for more information. + + Here is an example of a valid $JSON_BLOB: + { + "action": "FUNCTION.NAME", + "action_variables": {"INPUT": "some input", "PARAMETER_NAME": "some value", "PARAMETER_NAME_2": "42"} + } + [END USAGE INSTRUCTIONS] + [END INSTRUCTION] + + [THOUGHT PROCESS] + [QUESTION] + The input question I must answer + [THOUGHT] + To solve this problem, I should carefully analyze the given question and identify the necessary steps. Any facts I discover earlier in my thought process should be repeated here to keep them readily available. + If there is function which can be leveraged for validation, use it in ACTION before jumping into FINAL ANSWER. + [ACTION] + $JSON_BLOB + [OBSERVATION] + The result of the action will be provided here. It could be result or error message of the action, or chat history between assistant and user to tackle the problem. + ... (These THOUGHT/ACTION/OBSERVATION can repeat until the final answer is reached.) + [FINAL ANSWER] + Once I have gathered all the necessary observations and performed any required actions, if there is a suitable function for validation, provide the final answer in JSON in the following format: + { "action": "$(last_action_name)", "action_variables": {"INPUT": "some input", "PARAMETER_NAME": "some value", "PARAMETER_NAME_2": "42"}} + If there is not a fitting function available to validate the result, I can provide the final answer in a clear and human-readable format. + [END THOUGHT PROCESS] + + IMOPRTANT REMINDER: your each response should contain only one next step and only single one $JSON_BLOB. Do not provide more than one step. + Let's break down the problem step by step and think about the best approach. + + Begin! + + [QUESTION] + {{$question}} + {{$agentScratchPad}} +description: Given a request or command or goal generate multi-step plan to reach the goal. After each step LLM is called to perform the reasoning for the next step. +name: ReActEngine +input_variables: + - name: question + description: The question to answer + - name: agentScratchPad + description: The agent's scratch pad + - name: functionDescriptions + description: The manual of the agent's functions +execution_settings: + text-davinci-003: + temperature: 0.0 + top_p: 1.0 + presence_penalty: 0.0 + frequency_penalty: 0.0 + max_tokens: 400 + stop_sequences: ["[OBSERVATION]", "[Observation]", "[QUESTION]"] + default: + temperature: 0.1 + top_p: 1.0 + presence_penalty: 0.0 + frequency_penalty: 0.0 + max_tokens: 400 + stop_sequences: ["[OBSERVATION]", "[Observation]", "[QUESTION]"] diff --git a/dotnet/src/Experimental/Orchestration.Flow/Plugins/ReActEngine.yaml b/dotnet/src/Experimental/Orchestration.Flow/Plugins/ReActEngine.yaml new file mode 100644 index 000000000000..6679bf411eda --- /dev/null +++ b/dotnet/src/Experimental/Orchestration.Flow/Plugins/ReActEngine.yaml @@ -0,0 +1,104 @@ +template_format: semantic-kernel +template: | + [INSTRUCTION] + Answer the following questions as accurately as possible using the provided functions. + + [AVAILABLE FUNCTIONS] + The function definitions below are in the following format: + : + - : + - ... + [END AVAILABLE FUNCTIONS] + + [USAGE INSTRUCTIONS] + To use the functions, specify a JSON blob representing an action. The JSON blob should contain fully qualified name of the function to use, and an "action_variables" key with a JSON object of string values to use when calling the function. + Do not call functions directly; they must be invoked through an action. + + Here is an example of a valid $JSON_BLOB: + ``` + { + "action": "_Namespace_.FUNCTION.NAME", + "action_variables": {"PARAMETER_NAME_1": "some value", "PARAMETER_NAME_2": "42"} + } + ``` + The keys of "action_variables" should match the defined [PARAMETERS] of the named "action" in [AVAILABLE FUNCTIONS]. + Dictionary values in "action_variables" must be strings and represent the actual values to be passed to the function. + Ensure that the $JSON_BLOB contains only a SINGLE action; do NOT return multiple actions. + IMPORTANT: + * Use only the available functions listed in the [AVAILABLE FUNCTIONS] section. + * Do not attempt to use any other functions that are not specified. + * The value of parameters should either by empty if the expectation is for the user to provide them and have not been provided yet, or derived from the agent scratchpad. + * You are not allowed to ask user directly for more information. + [END USAGE INSTRUCTIONS] + [END INSTRUCTION] + + [THOUGHT PROCESS] + [QUESTION] + The input question I must answer + [THOUGHT] + To solve this problem, I should carefully analyze the given question and identify the necessary steps. Any facts I discover earlier in my thought process should be repeated here to keep them readily available. + If there is function which can be leveraged for validation, use it in ACTION before jumping into FINAL ANSWER. + [ACTION] + $JSON_BLOB + [OBSERVATION] + The result of the action will be provided here. It could be result or error message of the action, or chat history between assistant and user to tackle the problem. + ... (These THOUGHT/ACTION/OBSERVATION can repeat until the final answer is reached.) + [FINAL ANSWER] + Once I have gathered all the necessary observations and performed any required actions, provide the final answer in a clear and human-readable format. + [END THOUGHT PROCESS] + + Example: + [AVAILABLE FUNCTIONS] + AuthorPlugin.WritePoem: useful to write poem given a style and input + - input: input for the poem + - style: style of the poem, leave empty if not specified + [END AVAILABLE FUNCTIONS] + [QUESTION] + Write a poem about sun in Whitman's style. + [THOUGHT] + I should use WritePoem function for it. + [ACTION] + { + "action": "AuthorPlugin.WritePoem", + "action_variables": { + "input": "sun", + "stype": "Whitman", + } + } + + IMPORTANT REMINDER: your each response should contain only one next step and only single one [ACTION] part. Do not provide more than one step. + Let's break down the problem step by step and think about the best approach. + + Begin! + + [AVAILABLE FUNCTIONS] + {{$functionDescriptions}} + [END AVAILABLE FUNCTIONS] + [QUESTION] + {{$question}} + {{$agentScratchPad}} + [THOUGHT] +description: Given a request or command or goal generate multi-step plan to reach the goal. After each step LLM is called to perform the reasoning for the next step. +name: ReActEngine +input_variables: + - name: question + description: The question to answer + - name: agentScratchPad + description: The agent's scratch pad + - name: functionDescriptions + description: The manual of the agent's functions +execution_settings: + text-davinci-003: + temperature: 0.0 + top_p: 1.0 + presence_penalty: 0.0 + frequency_penalty: 0.0 + max_tokens: 400 + stop_sequences: ["[OBSERVATION]", "[Observation]", "[QUESTION]", "[AVAILABLE FUNCTIONS]"] + default: + temperature: 0.1 + top_p: 1.0 + presence_penalty: 0.0 + frequency_penalty: 0.0 + max_tokens: 400 + stop_sequences: ["[OBSERVATION]", "[Observation]", "[QUESTION]", "[AVAILABLE FUNCTIONS]"] diff --git a/dotnet/src/Experimental/Orchestration.Flow/Plugins/ReActEngine/config.json b/dotnet/src/Experimental/Orchestration.Flow/Plugins/ReActEngine/config.json deleted file mode 100644 index 4fe286c7a5d7..000000000000 --- a/dotnet/src/Experimental/Orchestration.Flow/Plugins/ReActEngine/config.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "schema": 1, - "description": "Given a request or command or goal generate multi-step plan to reach the goal. After each step LLM is called to perform the reasoning for the next step.", - "execution_settings": { - "default": { - "max_tokens": 400, - "temperature": 0.1, - "top_p": 1, - "presence_penalty": 0.0, - "frequency_penalty": 0.0, - "stop_sequences": ["[OBSERVATION]", "[Observation]", "[QUESTION]"] - } - }, - "input_variables": [ - { - "name": "question", - "description": "The question to answer", - "default": "" - }, - { - "name": "agentScratchPad", - "description": "The agent's scratch pad", - "default": "" - }, - { - "name": "functionDescriptions", - "description": "The manual of the agent's functions", - "default": "" - } - ] -} diff --git a/dotnet/src/Experimental/Orchestration.Flow/Plugins/ReActEngine/gpt4/config.json b/dotnet/src/Experimental/Orchestration.Flow/Plugins/ReActEngine/gpt4/config.json deleted file mode 100644 index 79023cde4925..000000000000 --- a/dotnet/src/Experimental/Orchestration.Flow/Plugins/ReActEngine/gpt4/config.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "schema": 1, - "description": - "Given a request or command or goal generate multi-step plan to reach the goal. After each step LLM is called to perform the reasoning for the next step.", - "execution_settings": { - "default": { - "max_tokens": 400, - "temperature": 0.1, - "top_p": 1, - "presence_penalty": 0.0, - "frequency_penalty": 0.0, - "stop_sequences": ["[OBSERVATION]"] - } - }, - "input_variables": [ - { - "name": "question", - "description": "The question to answer", - "default": "" - }, - { - "name": "agentScratchPad", - "description": "The agent's scratch pad", - "default": "" - }, - { - "name": "functionDescriptions", - "description": "The manual of the agent's functions", - "default": "" - } - ] -} \ No newline at end of file diff --git a/dotnet/src/Experimental/Orchestration.Flow/Plugins/ReActEngine/gpt4/skprompt.txt b/dotnet/src/Experimental/Orchestration.Flow/Plugins/ReActEngine/gpt4/skprompt.txt deleted file mode 100644 index 4bd4b821b940..000000000000 --- a/dotnet/src/Experimental/Orchestration.Flow/Plugins/ReActEngine/gpt4/skprompt.txt +++ /dev/null @@ -1,55 +0,0 @@ -[INSTRUCTION] -Answer the following questions as accurately as possible using the provided functions. - -[AVAILABLE FUNCTIONS] -The function definitions below are in the following format: -: - - : - - ... - -{{$functionDescriptions}} -[END AVAILABLE FUNCTIONS] - -[USAGE INSTRUCTIONS] -To use the functions, specify a JSON blob representing an action. The JSON blob should contain fully qualified name of the function to use, and an "action_variables" key with a JSON object of string values to use when calling the function. -Do not call functions directly; they must be invoked through an action. -The "action_variables" values should match the defined [PARAMETERS] of the named "action" in [AVAILABLE FUNCTIONS]. -Dictionary values in "action_variables" must be strings and represent the actual values to be passed to the function. -Ensure that the $JSON_BLOB contains only a SINGLE action; do NOT return multiple actions. -IMPORTANT: Use only the available functions listed in the [AVAILABLE FUNCTIONS] section. Do not attempt to use any other functions that are not specified. -The value of parameters should either by empty if the expectation is for the user to provide them and have not been provided yet, or derived from the agent scratchpad. -You are not allowed to ask user directly for more information. - -Here is an example of a valid $JSON_BLOB: -{ - "action": "FUNCTION.NAME", - "action_variables": {"INPUT": "some input", "PARAMETER_NAME": "some value", "PARAMETER_NAME_2": "42"} -} -[END USAGE INSTRUCTIONS] -[END INSTRUCTION] - -[THOUGHT PROCESS] -[QUESTION] -The input question I must answer -[THOUGHT] -To solve this problem, I should carefully analyze the given question and identify the necessary steps. Any facts I discover earlier in my thought process should be repeated here to keep them readily available. -If there is function which can be leveraged for validation, use it in ACTION before jumping into FINAL ANSWER. -[ACTION] -$JSON_BLOB -[OBSERVATION] -The result of the action will be provided here. It could be result or error message of the action, or chat history between assistant and user to tackle the problem. -... (These THOUGHT/ACTION/OBSERVATION can repeat until the final answer is reached.) -[FINAL ANSWER] -Once I have gathered all the necessary observations and performed any required actions, if there is a suitable function for validation, provide the final answer in JSON in the following format: -{ "action": "$(last_action_name)", "action_variables": {"INPUT": "some input", "PARAMETER_NAME": "some value", "PARAMETER_NAME_2": "42"}} -If there is not a fitting function available to validate the result, I can provide the final answer in a clear and human-readable format. -[END THOUGHT PROCESS] - -IMOPRTANT REMINDER: your each response should contain only one next step and only single one $JSON_BLOB. Do not provide more than one step. -Let's break down the problem step by step and think about the best approach. - -Begin! - -[Question] -{{$question}} -{{$agentScratchPad}} diff --git a/dotnet/src/Experimental/Orchestration.Flow/Plugins/ReActEngine/skprompt.txt b/dotnet/src/Experimental/Orchestration.Flow/Plugins/ReActEngine/skprompt.txt deleted file mode 100644 index eff7607278ce..000000000000 --- a/dotnet/src/Experimental/Orchestration.Flow/Plugins/ReActEngine/skprompt.txt +++ /dev/null @@ -1,78 +0,0 @@ -[INSTRUCTION] -Answer the following questions as accurately as possible using the provided functions. - -[AVAILABLE FUNCTIONS] -The function definitions below are in the following format: -: - - : - - ... -[END AVAILABLE FUNCTIONS] - -[USAGE INSTRUCTIONS] -To use the functions, specify a JSON blob representing an action. The JSON blob should contain fully qualified name of the function to use, and an "action_variables" key with a JSON object of string values to use when calling the function. -Do not call functions directly; they must be invoked through an action. - -Here is an example of a valid $JSON_BLOB: -``` -{ - "action": "_Namespace_.FUNCTION.NAME", - "action_variables": {"PARAMETER_NAME_1": "some value", "PARAMETER_NAME_2": "42"} -} -``` -The keys of "action_variables" should match the defined [PARAMETERS] of the named "action" in [AVAILABLE FUNCTIONS]. -Dictionary values in "action_variables" must be strings and represent the actual values to be passed to the function. -Ensure that the $JSON_BLOB contains only a SINGLE action; do NOT return multiple actions. -IMPORTANT: -* Use only the available functions listed in the [AVAILABLE FUNCTIONS] section. -* Do not attempt to use any other functions that are not specified. -* The value of parameters should either by empty if the expectation is for the user to provide them and have not been provided yet, or derived from the agent scratchpad. -* You are not allowed to ask user directly for more information. -[END USAGE INSTRUCTIONS] -[END INSTRUCTION] - -[THOUGHT PROCESS] -[QUESTION] -The input question I must answer -[THOUGHT] -To solve this problem, I should carefully analyze the given question and identify the necessary steps. Any facts I discover earlier in my thought process should be repeated here to keep them readily available. -If there is function which can be leveraged for validation, use it in ACTION before jumping into FINAL ANSWER. -[ACTION] -$JSON_BLOB -[OBSERVATION] -The result of the action will be provided here. It could be result or error message of the action, or chat history between assistant and user to tackle the problem. -... (These THOUGHT/ACTION/OBSERVATION can repeat until the final answer is reached.) -[FINAL ANSWER] -Once I have gathered all the necessary observations and performed any required actions, provide the final answer in a clear and human-readable format. -[END THOUGHT PROCESS] - -Example: -[AVAILABLE FUNCTIONS] -AuthorPlugin.WritePoem: useful to write poem given a style and input - - input: input for the poem - - style: style of the poem, leave empty if not specified -[END AVAILABLE FUNCTIONS] -[Question] -Write a poem about sun in Whitman's style. -[THOUGHT] -I should use WritePoem function for it. -[ACTION] -{ - "action": "AuthorPlugin.WritePoem", - "action_variables": { - "input": "sun", - "stype": "Whitman", - } -} - -IMPORTANT REMINDER: your each response should contain only one next step and only single one [ACTION] part. Do not provide more than one step. -Let's break down the problem step by step and think about the best approach. - -Begin! - -[AVAILABLE FUNCTIONS] -{{$functionDescriptions}} -[END AVAILABLE FUNCTIONS] -[Question] -{{$question}} -{{$agentScratchPad}} -[THOUGHT] \ No newline at end of file diff --git a/dotnet/src/Functions/Functions.OpenApi/Extensions/OpenApiKernelExtensions.cs b/dotnet/src/Functions/Functions.OpenApi/Extensions/OpenApiKernelExtensions.cs index 5ef45e6f241b..4b574df4f45e 100644 --- a/dotnet/src/Functions/Functions.OpenApi/Extensions/OpenApiKernelExtensions.cs +++ b/dotnet/src/Functions/Functions.OpenApi/Extensions/OpenApiKernelExtensions.cs @@ -356,8 +356,11 @@ private static string ConvertOperationIdToValidFunctionName(string operationId, Verify.ValidFunctionName(operationId); return operationId; } - catch (KernelException) + catch (ArgumentException) { + // The exception indicates that the operationId is not a valid function name. + // To comply with the SK Function name requirements, it needs to be converted or sanitized. + // Therefore, it should not be re-thrown, but rather swallowed to allow the conversion below. } // Tokenize operation id on forward and back slashes diff --git a/dotnet/src/Functions/Functions.UnitTests/OpenApi/Extensions/KernelOpenApiPluginExtensionsTests.cs b/dotnet/src/Functions/Functions.UnitTests/OpenApi/Extensions/KernelOpenApiPluginExtensionsTests.cs index fbc6019bd188..c7c23abb55ab 100644 --- a/dotnet/src/Functions/Functions.UnitTests/OpenApi/Extensions/KernelOpenApiPluginExtensionsTests.cs +++ b/dotnet/src/Functions/Functions.UnitTests/OpenApi/Extensions/KernelOpenApiPluginExtensionsTests.cs @@ -225,6 +225,24 @@ public async Task ItShouldRespectRunAsyncCancellationTokenOnExecutionAsync() Assert.Equal("fake-content", response.Content); } + [Fact] + public async Task ItShouldSanitizeOperationNameAsync() + { + // Arrange + var openApiDocument = ResourcePluginsProvider.LoadFromResource("documentV3_0.json"); + + using var content = OpenApiTestHelper.ModifyOpenApiDocument(openApiDocument, (doc) => + { + doc["paths"]!["/secrets/{secret-name}"]!["get"]!["operationId"] = "issues/create-mile.stone"; + }); + + // Act + var plugin = await this._kernel.ImportPluginFromOpenApiAsync("fakePlugin", content, this._executionParameters); + + // Assert + Assert.True(plugin.TryGetFunction("IssuesCreatemilestone", out var _)); + } + public void Dispose() { this._openApiDocument.Dispose(); diff --git a/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAIToolsTests.cs b/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAIToolsTests.cs index 28a30e1546a8..5e30b776d85a 100644 --- a/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAIToolsTests.cs +++ b/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAIToolsTests.cs @@ -37,11 +37,15 @@ public async Task CanAutoInvokeKernelFunctionsAsync() kernel.ImportPluginFromType(); var invokedFunctions = new List(); + +#pragma warning disable CS0618 // Events are deprecated void MyInvokingHandler(object? sender, FunctionInvokingEventArgs e) { invokedFunctions.Add(e.Function.Name); } + kernel.FunctionInvoking += MyInvokingHandler; +#pragma warning restore CS0618 // Events are deprecated // Act OpenAIPromptExecutionSettings settings = new() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }; @@ -60,11 +64,15 @@ public async Task CanAutoInvokeKernelFunctionsStreamingAsync() kernel.ImportPluginFromType(); var invokedFunctions = new List(); + +#pragma warning disable CS0618 // Events are deprecated void MyInvokingHandler(object? sender, FunctionInvokingEventArgs e) { invokedFunctions.Add($"{e.Function.Name}({string.Join(", ", e.Arguments)})"); } + kernel.FunctionInvoking += MyInvokingHandler; +#pragma warning restore CS0618 // Events are deprecated // Act OpenAIPromptExecutionSettings settings = new() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }; diff --git a/dotnet/src/InternalUtilities/src/System/NonNullCollection.cs b/dotnet/src/InternalUtilities/src/System/NonNullCollection.cs new file mode 100644 index 000000000000..ae9efbe969b9 --- /dev/null +++ b/dotnet/src/InternalUtilities/src/System/NonNullCollection.cs @@ -0,0 +1,143 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.SemanticKernel; + +/// +/// Provides a collection of non-null items. +/// +[ExcludeFromCodeCoverage] +[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "This class is an internal utility.")] +internal sealed class NonNullCollection : IList, IReadOnlyList +{ + /// + /// The underlying list of items. + /// + private readonly List _items; + + /// + /// Initializes a new instance of the class. + /// + public NonNullCollection() => this._items = new(); + + /// + /// Initializes a new instance of the class. + /// + /// The initial collection of items to populate this collection. + public NonNullCollection(IEnumerable items) + { + Verify.NotNull(items); + this._items = new(items); + } + + /// + /// Gets or sets the item at the specified index in the collection. + /// + /// The index of the item to get or set. + /// The item at the specified index. + /// is null. + /// The was not valid for this collection. + public T this[int index] + { + get => this._items[index]; + set + { + Verify.NotNull(value); + this._items[index] = value; + } + } + + /// + /// Gets the number of items in the collection. + /// + public int Count => this._items.Count; + + /// + /// Adds an item to the collection. + /// + /// The item to add. + /// is null. + public void Add(T item) + { + Verify.NotNull(item); + this._items.Add(item); + } + + /// + /// Removes all items from the collection. + /// + public void Clear() => this._items.Clear(); + + /// + /// Determines whether an item is in the collection. + /// + /// The item to locate. + /// True if the item is found in the collection; otherwise, false. + /// is null. + public bool Contains(T item) + { + Verify.NotNull(item); + return this._items.Contains(item); + } + + /// + /// Copies all of the items in the collection to an array, starting at the specified destination array index. + /// + /// The destination array into which the items should be copied. + /// The zero-based index into at which copying should begin. + /// is null. + /// The number of items in the collection is greater than the available space from to the end of . + /// is less than 0. + public void CopyTo(T[] array, int arrayIndex) => this._items.CopyTo(array, arrayIndex); + + /// + /// Searches for the specified item and returns the index of the first occurrence. + /// + /// The item to locate. + /// The index of the first found occurrence of the specified item; -1 if the item could not be found. + public int IndexOf(T item) + { + Verify.NotNull(item); + return this._items.IndexOf(item); + } + + /// + /// Inserts an item into the collection at the specified index. + /// + /// The index at which the item should be inserted. + /// The item to insert. + /// is null. + public void Insert(int index, T item) + { + Verify.NotNull(item); + this._items.Insert(index, item); + } + + /// + /// Removes the first occurrence of the specified item from the collection. + /// + /// The item to remove from the collection. + /// True if the item was successfully removed; false if it wasn't located in the collection. + /// is null. + public bool Remove(T item) + { + Verify.NotNull(item); + return this._items.Remove(item); + } + + /// + /// Removes the item at the specified index from the collection. + /// + /// The index of the item to remove. + public void RemoveAt(int index) => this._items.RemoveAt(index); + + bool ICollection.IsReadOnly => false; + + IEnumerator IEnumerable.GetEnumerator() => this._items.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => this._items.GetEnumerator(); +} diff --git a/dotnet/src/SemanticKernel.Abstractions/Events/CancelKernelEventArgs.cs b/dotnet/src/SemanticKernel.Abstractions/Events/CancelKernelEventArgs.cs index 606381266422..ed07decf7f27 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Events/CancelKernelEventArgs.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Events/CancelKernelEventArgs.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; namespace Microsoft.SemanticKernel; @@ -10,7 +9,7 @@ namespace Microsoft.SemanticKernel; /// Provides an for cancelable operations related /// to -based operations. /// -[Experimental("SKEXP0004")] +[Obsolete("Events are deprecated in favor of filters. Example in dotnet/samples/KernelSyntaxExamples/Getting_Started/Step7_Observability.cs of Semantic Kernel repository.")] public abstract class CancelKernelEventArgs : KernelEventArgs { /// diff --git a/dotnet/src/SemanticKernel.Abstractions/Events/FunctionInvokedEventArgs.cs b/dotnet/src/SemanticKernel.Abstractions/Events/FunctionInvokedEventArgs.cs index 56756fa36fc1..0317cb5cf860 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Events/FunctionInvokedEventArgs.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Events/FunctionInvokedEventArgs.cs @@ -1,14 +1,13 @@ // Copyright (c) Microsoft. All rights reserved. using System; -using System.Diagnostics.CodeAnalysis; namespace Microsoft.SemanticKernel; /// /// Provides a used in events just after a function is invoked. /// -[Experimental("SKEXP0004")] +[Obsolete("Events are deprecated in favor of filters. Example in dotnet/samples/KernelSyntaxExamples/Getting_Started/Step7_Observability.cs of Semantic Kernel repository.")] public sealed class FunctionInvokedEventArgs : CancelKernelEventArgs { /// diff --git a/dotnet/src/SemanticKernel.Abstractions/Events/FunctionInvokingEventArgs.cs b/dotnet/src/SemanticKernel.Abstractions/Events/FunctionInvokingEventArgs.cs index 91be92a15604..99396a137bfe 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Events/FunctionInvokingEventArgs.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Events/FunctionInvokingEventArgs.cs @@ -1,13 +1,13 @@ // Copyright (c) Microsoft. All rights reserved. -using System.Diagnostics.CodeAnalysis; +using System; namespace Microsoft.SemanticKernel; /// /// Provides a used in events just before a function is invoked. /// -[Experimental("SKEXP0004")] +[Obsolete("Events are deprecated in favor of filters. Example in dotnet/samples/KernelSyntaxExamples/Getting_Started/Step7_Observability.cs of Semantic Kernel repository.")] public sealed class FunctionInvokingEventArgs : CancelKernelEventArgs { /// diff --git a/dotnet/src/SemanticKernel.Abstractions/Events/KernelEventArgs.cs b/dotnet/src/SemanticKernel.Abstractions/Events/KernelEventArgs.cs index 062271d79d4a..6c659dc53f33 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Events/KernelEventArgs.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Events/KernelEventArgs.cs @@ -2,12 +2,11 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; namespace Microsoft.SemanticKernel; /// Provides an for operations related to -based operations. -[Experimental("SKEXP0004")] +[Obsolete("Events are deprecated in favor of filters. Example in dotnet/samples/KernelSyntaxExamples/Getting_Started/Step7_Observability.cs of Semantic Kernel repository.")] public abstract class KernelEventArgs : EventArgs { /// diff --git a/dotnet/src/SemanticKernel.Abstractions/Events/PromptRenderedEventArgs.cs b/dotnet/src/SemanticKernel.Abstractions/Events/PromptRenderedEventArgs.cs index 68f2b3acef46..83f14a76aafd 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Events/PromptRenderedEventArgs.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Events/PromptRenderedEventArgs.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. +using System; using System.Diagnostics.CodeAnalysis; namespace Microsoft.SemanticKernel; @@ -7,7 +8,7 @@ namespace Microsoft.SemanticKernel; /// /// Provides a used in events raised just after a prompt has been rendered. /// -[Experimental("SKEXP0004")] +[Obsolete("Events are deprecated in favor of filters. Example in dotnet/samples/KernelSyntaxExamples/Getting_Started/Step7_Observability.cs of Semantic Kernel repository.")] public sealed class PromptRenderedEventArgs : CancelKernelEventArgs { private string _renderedPrompt; @@ -21,8 +22,7 @@ public sealed class PromptRenderedEventArgs : CancelKernelEventArgs public PromptRenderedEventArgs(KernelFunction function, KernelArguments arguments, string renderedPrompt) : base(function, arguments, metadata: null) { - Verify.NotNull(renderedPrompt); - this._renderedPrompt = renderedPrompt; + this.RenderedPrompt = renderedPrompt; } /// Gets or sets the rendered prompt. @@ -35,9 +35,10 @@ public PromptRenderedEventArgs(KernelFunction function, KernelArguments argument public string RenderedPrompt { get => this._renderedPrompt; + [MemberNotNull(nameof(_renderedPrompt))] set { - Verify.NotNull(value); + Verify.NotNullOrWhiteSpace(value); this._renderedPrompt = value; } } diff --git a/dotnet/src/SemanticKernel.Abstractions/Events/PromptRenderingEventArgs.cs b/dotnet/src/SemanticKernel.Abstractions/Events/PromptRenderingEventArgs.cs index 77259a2b3594..b808a6e8c293 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Events/PromptRenderingEventArgs.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Events/PromptRenderingEventArgs.cs @@ -1,13 +1,13 @@ // Copyright (c) Microsoft. All rights reserved. -using System.Diagnostics.CodeAnalysis; +using System; namespace Microsoft.SemanticKernel; /// /// Provides a used in events raised just before a prompt is rendered. /// -[Experimental("SKEXP0004")] +[Obsolete("Events are deprecated in favor of filters. Example in dotnet/samples/KernelSyntaxExamples/Getting_Started/Step7_Observability.cs of Semantic Kernel repository.")] public sealed class PromptRenderingEventArgs : KernelEventArgs { /// diff --git a/dotnet/src/SemanticKernel.Abstractions/Filters/Function/FunctionFilterContext.cs b/dotnet/src/SemanticKernel.Abstractions/Filters/Function/FunctionFilterContext.cs new file mode 100644 index 000000000000..e75093b7a678 --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/Filters/Function/FunctionFilterContext.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.SemanticKernel; + +/// +/// Base class with data related to function invocation. +/// +[Experimental("SKEXP0004")] +public abstract class FunctionFilterContext +{ + /// + /// Initializes a new instance of the class. + /// + /// The with which this filter is associated. + /// The arguments associated with the operation. + /// A dictionary of metadata associated with the operation. + internal FunctionFilterContext(KernelFunction function, KernelArguments arguments, IReadOnlyDictionary? metadata) + { + Verify.NotNull(function); + Verify.NotNull(arguments); + + this.Function = function; + this.Arguments = arguments; + this.Metadata = metadata; + } + + /// + /// Gets the with which this filter is associated. + /// + public KernelFunction Function { get; } + + /// + /// Gets the arguments associated with the operation. + /// + public KernelArguments Arguments { get; } + + /// + /// Gets a dictionary of metadata associated with the operation. + /// + public IReadOnlyDictionary? Metadata { get; } + + /// + /// Gets or sets a value indicating whether the operation associated with + /// the filter should be canceled. + /// + /// + /// The filter may set to true to indicate that the operation should + /// be canceled. If there are multiple filters registered, subsequent filters + /// may see and change a value set by a previous filter. The final result is what will + /// be considered by the component that triggers filter. + /// + public bool Cancel { get; set; } +} diff --git a/dotnet/src/SemanticKernel.Abstractions/Filters/Function/FunctionInvokedContext.cs b/dotnet/src/SemanticKernel.Abstractions/Filters/Function/FunctionInvokedContext.cs new file mode 100644 index 000000000000..beefaab2b9b1 --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/Filters/Function/FunctionInvokedContext.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.SemanticKernel; + +/// +/// Class with data related to function after invocation. +/// +[Experimental("SKEXP0004")] +public sealed class FunctionInvokedContext : FunctionFilterContext +{ + /// + /// Initializes a new instance of the class. + /// + /// The arguments associated with the operation. + /// The result of the function's invocation. + public FunctionInvokedContext(KernelArguments arguments, FunctionResult result) + : base(result.Function, arguments, (result ?? throw new ArgumentNullException(nameof(result))).Metadata) + { + this.Result = result; + this.ResultValue = result.Value; + } + + /// + /// Gets the result of the function's invocation. + /// + public FunctionResult Result { get; } + + /// + /// Gets the raw result of the function's invocation. + /// + internal object? ResultValue { get; private set; } + + /// + /// Sets an object to use as the overridden new result for the function's invocation. + /// + /// The value to use as the new result of the function's invocation. + public void SetResultValue(object? value) + { + this.ResultValue = value; + } +} diff --git a/dotnet/src/SemanticKernel.Abstractions/Filters/Function/FunctionInvokingContext.cs b/dotnet/src/SemanticKernel.Abstractions/Filters/Function/FunctionInvokingContext.cs new file mode 100644 index 000000000000..47067ded0389 --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/Filters/Function/FunctionInvokingContext.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.SemanticKernel; + +/// +/// Class with data related to function before invocation. +/// +[Experimental("SKEXP0004")] +public sealed class FunctionInvokingContext : FunctionFilterContext +{ + /// + /// Initializes a new instance of the class. + /// + /// The with which this filter is associated. + /// The arguments associated with the operation. + public FunctionInvokingContext(KernelFunction function, KernelArguments arguments) + : base(function, arguments, metadata: null) + { + } +} diff --git a/dotnet/src/SemanticKernel.Abstractions/Filters/Function/IFunctionFilter.cs b/dotnet/src/SemanticKernel.Abstractions/Filters/Function/IFunctionFilter.cs new file mode 100644 index 000000000000..8914f10ca675 --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/Filters/Function/IFunctionFilter.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.SemanticKernel; + +/// +/// Interface for filtering actions during function invocation. +/// +[Experimental("SKEXP0004")] +public interface IFunctionFilter +{ + /// + /// Method which is executed before function invocation. + /// + /// Data related to function before invocation. + void OnFunctionInvoking(FunctionInvokingContext context); + + /// + /// Method which is executed after function invocation. + /// + /// Data related to function after invocation. + void OnFunctionInvoked(FunctionInvokedContext context); +} diff --git a/dotnet/src/SemanticKernel.Abstractions/Filters/Prompt/IPromptFilter.cs b/dotnet/src/SemanticKernel.Abstractions/Filters/Prompt/IPromptFilter.cs new file mode 100644 index 000000000000..824fc18dd817 --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/Filters/Prompt/IPromptFilter.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.SemanticKernel; + +/// +/// Interface for filtering actions during prompt rendering. +/// +[Experimental("SKEXP0004")] +public interface IPromptFilter +{ + /// + /// Method which is executed before prompt rendering. + /// + /// Data related to prompt before rendering. + void OnPromptRendering(PromptRenderingContext context); + + /// + /// Method which is executed after prompt rendering. + /// + /// Data related to prompt after rendering. + void OnPromptRendered(PromptRenderedContext context); +} diff --git a/dotnet/src/SemanticKernel.Abstractions/Filters/Prompt/PromptFilterContext.cs b/dotnet/src/SemanticKernel.Abstractions/Filters/Prompt/PromptFilterContext.cs new file mode 100644 index 000000000000..7b4090404afe --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/Filters/Prompt/PromptFilterContext.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.SemanticKernel; + +/// +/// Base class with data related to prompt rendering. +/// +[Experimental("SKEXP0004")] +public abstract class PromptFilterContext +{ + /// + /// Initializes a new instance of the class. + /// + /// The with which this filter is associated. + /// The arguments associated with the operation. + /// A dictionary of metadata associated with the operation. + internal PromptFilterContext(KernelFunction function, KernelArguments arguments, IReadOnlyDictionary? metadata) + { + Verify.NotNull(function); + Verify.NotNull(arguments); + + this.Function = function; + this.Arguments = arguments; + this.Metadata = metadata; + } + + /// + /// Gets the with which this filter is associated. + /// + public KernelFunction Function { get; } + + /// + /// Gets the arguments associated with the operation. + /// + public KernelArguments Arguments { get; } + + /// + /// Gets a dictionary of metadata associated with the operation. + /// + public IReadOnlyDictionary? Metadata { get; } +} diff --git a/dotnet/src/SemanticKernel.Abstractions/Filters/Prompt/PromptRenderedContext.cs b/dotnet/src/SemanticKernel.Abstractions/Filters/Prompt/PromptRenderedContext.cs new file mode 100644 index 000000000000..e14e685c9181 --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/Filters/Prompt/PromptRenderedContext.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.SemanticKernel; + +/// +/// Class with data related to prompt after rendering. +/// +[Experimental("SKEXP0004")] +public sealed class PromptRenderedContext : PromptFilterContext +{ + private string _renderedPrompt; + + /// + /// Initializes a new instance of the class. + /// + /// The with which this filter is associated. + /// The arguments associated with the operation. + /// The prompt that was rendered by the associated operation. + public PromptRenderedContext(KernelFunction function, KernelArguments arguments, string renderedPrompt) + : base(function, arguments, metadata: null) + { + this.RenderedPrompt = renderedPrompt; + } + + /// + /// Gets or sets a value indicating whether the operation associated with + /// the filter should be canceled. + /// + /// + /// The filter may set to true to indicate that the operation should + /// be canceled. If there are multiple filters registered, subsequent filters + /// may see and change a value set by a previous filter. The final result is what will + /// be considered by the component that triggers filter. + /// + public bool Cancel { get; set; } + + /// + /// Gets or sets the rendered prompt. + /// + /// + /// The filter may view the rendered prompt and change it, if desired. + /// If there are multiple filters registered, subsequent filters may + /// overwrite a value set by a previous filter. The final result is what will + /// be the prompt used by the system. + /// + public string RenderedPrompt + { + get => this._renderedPrompt; + [MemberNotNull(nameof(_renderedPrompt))] + set + { + Verify.NotNullOrWhiteSpace(value); + this._renderedPrompt = value; + } + } +} diff --git a/dotnet/src/SemanticKernel.Abstractions/Filters/Prompt/PromptRenderingContext.cs b/dotnet/src/SemanticKernel.Abstractions/Filters/Prompt/PromptRenderingContext.cs new file mode 100644 index 000000000000..93ac57b32151 --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/Filters/Prompt/PromptRenderingContext.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.SemanticKernel; + +/// +/// Class with data related to prompt before rendering. +/// +[Experimental("SKEXP0004")] +public sealed class PromptRenderingContext : PromptFilterContext +{ + /// + /// Initializes a new instance of the class. + /// + /// The with which this filter is associated. + /// The arguments associated with the operation. + public PromptRenderingContext(KernelFunction function, KernelArguments arguments) + : base(function, arguments, metadata: null) + { + } +} diff --git a/dotnet/src/SemanticKernel.Abstractions/Functions/KernelFunction.cs b/dotnet/src/SemanticKernel.Abstractions/Functions/KernelFunction.cs index 077668c39509..30cace107c82 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Functions/KernelFunction.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Functions/KernelFunction.cs @@ -133,27 +133,56 @@ public async Task InvokeAsync( cancellationToken.ThrowIfCancellationRequested(); // Invoke pre-invocation event handler. If it requests cancellation, throw. - if (kernel.OnFunctionInvoking(this, arguments)?.Cancel is true) +#pragma warning disable CS0618 // Events are deprecated + var invokingEventArgs = kernel.OnFunctionInvoking(this, arguments); +#pragma warning restore CS0618 // Events are deprecated + + // Invoke pre-invocation filter. If it requests cancellation, throw. + var invokingContext = kernel.OnFunctionInvokingFilter(this, arguments); + + if (invokingEventArgs?.Cancel is true) { throw new OperationCanceledException($"A {nameof(Kernel)}.{nameof(Kernel.FunctionInvoking)} event handler requested cancellation before function invocation."); } + if (invokingContext?.Cancel is true) + { + throw new OperationCanceledException("A function filter requested cancellation before function invocation."); + } + // Invoke the function. functionResult = await this.InvokeCoreAsync(kernel, arguments, cancellationToken).ConfigureAwait(false); // Invoke the post-invocation event handler. If it requests cancellation, throw. +#pragma warning disable CS0618 // Events are deprecated var invokedEventArgs = kernel.OnFunctionInvoked(this, arguments, functionResult); +#pragma warning restore CS0618 // Events are deprecated + + // Invoke the post-invocation filter. If it requests cancellation, throw. + var invokedContext = kernel.OnFunctionInvokedFilter(arguments, functionResult); + if (invokedEventArgs is not null) { // Apply any changes from the event handlers to final result. functionResult = new FunctionResult(this, invokedEventArgs.ResultValue, functionResult.Culture, invokedEventArgs.Metadata ?? functionResult.Metadata); } + if (invokedContext is not null) + { + // Apply any changes from the function filters to final result. + functionResult = new FunctionResult(this, invokedContext.ResultValue, functionResult.Culture, invokedContext.Metadata ?? functionResult.Metadata); + } + if (invokedEventArgs?.Cancel is true) { throw new OperationCanceledException($"A {nameof(Kernel)}.{nameof(Kernel.FunctionInvoked)} event handler requested cancellation after function invocation."); } + if (invokedContext?.Cancel is true) + { + throw new OperationCanceledException("A function filter requested cancellation after function invocation."); + } + logger.LogFunctionInvokedSuccess(this.Name); logger.LogFunctionResultValue(functionResult.Value); @@ -248,12 +277,23 @@ public async IAsyncEnumerable InvokeStreamingAsync( cancellationToken.ThrowIfCancellationRequested(); // Invoke pre-invocation event handler. If it requests cancellation, throw. +#pragma warning disable CS0618 // Events are deprecated var invokingEventArgs = kernel.OnFunctionInvoking(this, arguments); - if (invokingEventArgs is not null && invokingEventArgs.Cancel) +#pragma warning restore CS0618 // Events are deprecated + + // Invoke pre-invocation filter. If it requests cancellation, throw. + var invokingContext = kernel.OnFunctionInvokingFilter(this, arguments); + + if (invokingEventArgs?.Cancel is true) { throw new OperationCanceledException($"A {nameof(Kernel)}.{nameof(Kernel.FunctionInvoking)} event handler requested cancellation before function invocation."); } + if (invokingContext?.Cancel is true) + { + throw new OperationCanceledException("A function filter requested cancellation before function invocation."); + } + // Invoke the function and get its streaming enumerator. enumerator = this.InvokeStreamingCoreAsync(kernel, arguments, cancellationToken).GetAsyncEnumerator(cancellationToken); @@ -291,7 +331,7 @@ public async IAsyncEnumerable InvokeStreamingAsync( } } - // The FunctionInvoked hook is not used when streaming. + // The FunctionInvoked hook and filter are not used when streaming. } finally { diff --git a/dotnet/src/SemanticKernel.Abstractions/Kernel.cs b/dotnet/src/SemanticKernel.Abstractions/Kernel.cs index a8f981f2f20a..46a60aa4a11a 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Kernel.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Kernel.cs @@ -32,6 +32,10 @@ public sealed class Kernel private CultureInfo _culture = CultureInfo.InvariantCulture; /// The collection of plugins, initialized via the constructor or lazily-initialized on first access via . private KernelPluginCollection? _plugins; + /// The collection of function filters, initialized via the constructor or lazily-initialized on first access via . + private NonNullCollection? _functionFilters; + /// The collection of prompt filters, initialized via the constructor or lazily-initialized on first access via . + private NonNullCollection? _promptFilters; /// /// Initializes a new instance of . @@ -54,6 +58,7 @@ public Kernel( // Store the provided plugins. If there weren't any, look in DI to see if there's a plugin collection. this._plugins = plugins ?? this.Services.GetService(); + if (this._plugins is null) { // Otherwise, enumerate any plugins that may have been registered directly. @@ -67,6 +72,22 @@ public Kernel( this._plugins = new(e); } } + + // Enumerate any function filters that may have been registered. + IEnumerable functionFilters = this.Services.GetServices(); + + if (functionFilters is not ICollection functionFilterCollection || functionFilterCollection.Count != 0) + { + this._functionFilters = new(functionFilters); + } + + // Enumerate any prompt filters that may have been registered. + IEnumerable promptFilters = this.Services.GetServices(); + + if (promptFilters is not ICollection promptFilterCollection || promptFilterCollection.Count != 0) + { + this._promptFilters = new(promptFilters); + } } /// Creates a builder for constructing instances. @@ -116,6 +137,24 @@ public Kernel Clone() => Interlocked.CompareExchange(ref this._plugins, new KernelPluginCollection(), null) ?? this._plugins; + /// + /// Gets the collection of function filters available through the kernel. + /// + [Experimental("SKEXP0004")] + public IList FunctionFilters => + this._functionFilters ?? + Interlocked.CompareExchange(ref this._functionFilters, new NonNullCollection(), null) ?? + this._functionFilters; + + /// + /// Gets the collection of function filters available through the kernel. + /// + [Experimental("SKEXP0004")] + public IList PromptFilters => + this._promptFilters ?? + Interlocked.CompareExchange(ref this._promptFilters, new NonNullCollection(), null) ?? + this._promptFilters; + /// /// Gets the service provider used to query for services available through the kernel. /// @@ -166,30 +205,6 @@ public CultureInfo Culture Interlocked.CompareExchange(ref this._data, new Dictionary(), null) ?? this._data; - /// - /// Provides an event that's raised prior to a function's invocation. - /// - [Experimental("SKEXP0004")] - public event EventHandler? FunctionInvoking; - - /// - /// Provides an event that's raised after a function's invocation. - /// - [Experimental("SKEXP0004")] - public event EventHandler? FunctionInvoked; - - /// - /// Provides an event that's raised prior to a prompt being rendered. - /// - [Experimental("SKEXP0004")] - public event EventHandler? PromptRendering; - - /// - /// Provides an event that's raised after a prompt is rendered. - /// - [Experimental("SKEXP0004")] - public event EventHandler? PromptRendered; - #region GetServices /// Gets a required service from the provider. /// Specifies the type of the service to get. @@ -264,58 +279,80 @@ public IEnumerable GetAllServices() where T : class #endregion - #region Internal Event Helpers + #region Internal Filtering + [Experimental("SKEXP0004")] - internal FunctionInvokingEventArgs? OnFunctionInvoking(KernelFunction function, KernelArguments arguments) + internal FunctionInvokingContext? OnFunctionInvokingFilter(KernelFunction function, KernelArguments arguments) { - FunctionInvokingEventArgs? eventArgs = null; - if (this.FunctionInvoking is { } functionInvoking) + FunctionInvokingContext? context = null; + + if (this._functionFilters is { Count: > 0 }) { - eventArgs = new(function, arguments); - functionInvoking.Invoke(this, eventArgs); + context = new(function, arguments); + + for (int i = 0; i < this._functionFilters.Count; i++) + { + this._functionFilters[i].OnFunctionInvoking(context); + } } - return eventArgs; + return context; } [Experimental("SKEXP0004")] - internal FunctionInvokedEventArgs? OnFunctionInvoked(KernelFunction function, KernelArguments arguments, FunctionResult result) + internal FunctionInvokedContext? OnFunctionInvokedFilter(KernelArguments arguments, FunctionResult result) { - FunctionInvokedEventArgs? eventArgs = null; - if (this.FunctionInvoked is { } functionInvoked) + FunctionInvokedContext? context = null; + + if (this._functionFilters is { Count: > 0 }) { - eventArgs = new(function, arguments, result); - functionInvoked.Invoke(this, eventArgs); + context = new(arguments, result); + + for (int i = 0; i < this._functionFilters.Count; i++) + { + this._functionFilters[i].OnFunctionInvoked(context); + } } - return eventArgs; + return context; } [Experimental("SKEXP0004")] - internal PromptRenderingEventArgs? OnPromptRendering(KernelFunction function, KernelArguments arguments) + internal PromptRenderingContext? OnPromptRenderingFilter(KernelFunction function, KernelArguments arguments) { - PromptRenderingEventArgs? eventArgs = null; - if (this.PromptRendering is { } promptRendering) + PromptRenderingContext? context = null; + + if (this._promptFilters is { Count: > 0 }) { - eventArgs = new(function, arguments); - promptRendering.Invoke(this, eventArgs); + context = new(function, arguments); + + for (int i = 0; i < this._promptFilters.Count; i++) + { + this._promptFilters[i].OnPromptRendering(context); + } } - return eventArgs; + return context; } [Experimental("SKEXP0004")] - internal PromptRenderedEventArgs? OnPromptRendered(KernelFunction function, KernelArguments arguments, string renderedPrompt) + internal PromptRenderedContext? OnPromptRenderedFilter(KernelFunction function, KernelArguments arguments, string renderedPrompt) { - PromptRenderedEventArgs? eventArgs = null; - if (this.PromptRendered is { } promptRendered) + PromptRenderedContext? context = null; + + if (this._promptFilters is { Count: > 0 }) { - eventArgs = new(function, arguments, renderedPrompt); - promptRendered.Invoke(this, eventArgs); + context = new(function, arguments, renderedPrompt); + + for (int i = 0; i < this._promptFilters.Count; i++) + { + this._promptFilters[i].OnPromptRendered(context); + } } - return eventArgs; + return context; } + #endregion #region InvokeAsync @@ -523,4 +560,84 @@ public IAsyncEnumerable InvokeStreamingAsync( return function.InvokeStreamingAsync(this, arguments, cancellationToken); } #endregion + + #region Obsolete + + /// + /// Provides an event that's raised prior to a function's invocation. + /// + [Obsolete("Events are deprecated in favor of filters. Example in dotnet/samples/KernelSyntaxExamples/Getting_Started/Step7_Observability.cs of Semantic Kernel repository.")] + public event EventHandler? FunctionInvoking; + + /// + /// Provides an event that's raised after a function's invocation. + /// + [Obsolete("Events are deprecated in favor of filters. Example in dotnet/samples/KernelSyntaxExamples/Getting_Started/Step7_Observability.cs of Semantic Kernel repository.")] + public event EventHandler? FunctionInvoked; + + /// + /// Provides an event that's raised prior to a prompt being rendered. + /// + [Obsolete("Events are deprecated in favor of filters. Example in dotnet/samples/KernelSyntaxExamples/Getting_Started/Step7_Observability.cs of Semantic Kernel repository.")] + public event EventHandler? PromptRendering; + + /// + /// Provides an event that's raised after a prompt is rendered. + /// + [Obsolete("Events are deprecated in favor of filters. Example in dotnet/samples/KernelSyntaxExamples/Getting_Started/Step7_Observability.cs of Semantic Kernel repository.")] + public event EventHandler? PromptRendered; + + [Obsolete("Events are deprecated in favor of filters. Example in dotnet/samples/KernelSyntaxExamples/Getting_Started/Step7_Observability.cs of Semantic Kernel repository.")] + internal FunctionInvokingEventArgs? OnFunctionInvoking(KernelFunction function, KernelArguments arguments) + { + FunctionInvokingEventArgs? eventArgs = null; + if (this.FunctionInvoking is { } functionInvoking) + { + eventArgs = new(function, arguments); + functionInvoking.Invoke(this, eventArgs); + } + + return eventArgs; + } + + [Obsolete("Events are deprecated in favor of filters. Example in dotnet/samples/KernelSyntaxExamples/Getting_Started/Step7_Observability.cs of Semantic Kernel repository.")] + internal FunctionInvokedEventArgs? OnFunctionInvoked(KernelFunction function, KernelArguments arguments, FunctionResult result) + { + FunctionInvokedEventArgs? eventArgs = null; + if (this.FunctionInvoked is { } functionInvoked) + { + eventArgs = new(function, arguments, result); + functionInvoked.Invoke(this, eventArgs); + } + + return eventArgs; + } + + [Obsolete("Events are deprecated in favor of filters. Example in dotnet/samples/KernelSyntaxExamples/Getting_Started/Step7_Observability.cs of Semantic Kernel repository.")] + internal PromptRenderingEventArgs? OnPromptRendering(KernelFunction function, KernelArguments arguments) + { + PromptRenderingEventArgs? eventArgs = null; + if (this.PromptRendering is { } promptRendering) + { + eventArgs = new(function, arguments); + promptRendering.Invoke(this, eventArgs); + } + + return eventArgs; + } + + [Obsolete("Events are deprecated in favor of filters. Example in dotnet/samples/KernelSyntaxExamples/Getting_Started/Step7_Observability.cs of Semantic Kernel repository.")] + internal PromptRenderedEventArgs? OnPromptRendered(KernelFunction function, KernelArguments arguments, string renderedPrompt) + { + PromptRenderedEventArgs? eventArgs = null; + if (this.PromptRendered is { } promptRendered) + { + eventArgs = new(function, arguments, renderedPrompt); + promptRendered.Invoke(this, eventArgs); + } + + return eventArgs; + } + + #endregion } diff --git a/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromPrompt.cs b/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromPrompt.cs index 8c2252e9ea5b..4415afea8057 100644 --- a/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromPrompt.cs +++ b/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromPrompt.cs @@ -122,28 +122,36 @@ protected override async ValueTask InvokeCoreAsync( { this.AddDefaultValues(arguments); - (var aiService, var executionSettings, var renderedPrompt, var renderedEventArgs) = await this.RenderPromptAsync(kernel, arguments, cancellationToken).ConfigureAwait(false); - if (renderedEventArgs?.Cancel is true) + var result = await this.RenderPromptAsync(kernel, arguments, cancellationToken).ConfigureAwait(false); + +#pragma warning disable CS0612 // Events are deprecated + if (result.RenderedEventArgs?.Cancel is true) + { + throw new OperationCanceledException($"A {nameof(Kernel)}.{nameof(Kernel.PromptRendered)} event handler requested cancellation after prompt rendering."); + } +#pragma warning restore CS0612 // Events are deprecated + + if (result.RenderedContext?.Cancel is true) { - throw new OperationCanceledException($"A {nameof(Kernel)}.{nameof(Kernel.PromptRendered)} event handler requested cancellation before function invocation."); + throw new OperationCanceledException("A prompt filter requested cancellation after prompt rendering."); } - if (aiService is IChatCompletionService chatCompletion) + if (result.AIService is IChatCompletionService chatCompletion) { - var chatContent = await chatCompletion.GetChatMessageContentAsync(renderedPrompt, executionSettings, kernel, cancellationToken).ConfigureAwait(false); + var chatContent = await chatCompletion.GetChatMessageContentAsync(result.RenderedPrompt, result.ExecutionSettings, kernel, cancellationToken).ConfigureAwait(false); this.CaptureUsageDetails(chatContent.ModelId, chatContent.Metadata, this._logger); return new FunctionResult(this, chatContent, kernel.Culture, chatContent.Metadata); } - if (aiService is ITextGenerationService textGeneration) + if (result.AIService is ITextGenerationService textGeneration) { - var textContent = await textGeneration.GetTextContentWithDefaultParserAsync(renderedPrompt, executionSettings, kernel, cancellationToken).ConfigureAwait(false); + var textContent = await textGeneration.GetTextContentWithDefaultParserAsync(result.RenderedPrompt, result.ExecutionSettings, kernel, cancellationToken).ConfigureAwait(false); this.CaptureUsageDetails(textContent.ModelId, textContent.Metadata, this._logger); return new FunctionResult(this, textContent, kernel.Culture, textContent.Metadata); } // The service selector didn't find an appropriate service. This should only happen with a poorly implemented selector. - throw new NotSupportedException($"The AI service {aiService.GetType()} is not supported. Supported services are {typeof(IChatCompletionService)} and {typeof(ITextGenerationService)}"); + throw new NotSupportedException($"The AI service {result.AIService.GetType()} is not supported. Supported services are {typeof(IChatCompletionService)} and {typeof(ITextGenerationService)}"); } protected override async IAsyncEnumerable InvokeStreamingCoreAsync( @@ -153,25 +161,34 @@ protected override async IAsyncEnumerable InvokeStreamingCoreAsync? asyncReference = null; - if (aiService is IChatCompletionService chatCompletion) + + if (result.AIService is IChatCompletionService chatCompletion) { - asyncReference = chatCompletion.GetStreamingChatMessageContentsAsync(renderedPrompt, executionSettings, kernel, cancellationToken); + asyncReference = chatCompletion.GetStreamingChatMessageContentsAsync(result.RenderedPrompt, result.ExecutionSettings, kernel, cancellationToken); } - else if (aiService is ITextGenerationService textGeneration) + else if (result.AIService is ITextGenerationService textGeneration) { - asyncReference = textGeneration.GetStreamingTextContentsWithDefaultParserAsync(renderedPrompt, executionSettings, kernel, cancellationToken); + asyncReference = textGeneration.GetStreamingTextContentsWithDefaultParserAsync(result.RenderedPrompt, result.ExecutionSettings, kernel, cancellationToken); } else { // The service selector didn't find an appropriate service. This should only happen with a poorly implemented selector. - throw new NotSupportedException($"The AI service {aiService.GetType()} is not supported. Supported services are {typeof(IChatCompletionService)} and {typeof(ITextGenerationService)}"); + throw new NotSupportedException($"The AI service {result.AIService.GetType()} is not supported. Supported services are {typeof(IChatCompletionService)} and {typeof(ITextGenerationService)}"); } await foreach (var content in asyncReference) @@ -256,7 +273,7 @@ private void AddDefaultValues(KernelArguments arguments) } } - private async Task<(IAIService, PromptExecutionSettings?, string, PromptRenderedEventArgs?)> RenderPromptAsync(Kernel kernel, KernelArguments arguments, CancellationToken cancellationToken) + private async Task RenderPromptAsync(Kernel kernel, KernelArguments arguments, CancellationToken cancellationToken) { var serviceSelector = kernel.ServiceSelector; IAIService? aiService; @@ -277,7 +294,11 @@ private void AddDefaultValues(KernelArguments arguments) Verify.NotNull(aiService); +#pragma warning disable CS0618 // Events are deprecated kernel.OnPromptRendering(this, arguments); +#pragma warning restore CS0618 // Events are deprecated + + kernel.OnPromptRenderingFilter(this, arguments); var renderedPrompt = await this._promptTemplate.RenderAsync(kernel, arguments, cancellationToken).ConfigureAwait(false); @@ -286,21 +307,42 @@ private void AddDefaultValues(KernelArguments arguments) this._logger.LogTrace("Rendered prompt: {Prompt}", renderedPrompt); } +#pragma warning disable CS0618 // Events are deprecated var renderedEventArgs = kernel.OnPromptRendered(this, arguments, renderedPrompt); if (renderedEventArgs is not null && - renderedEventArgs.Cancel is false && + !renderedEventArgs.Cancel && renderedEventArgs.RenderedPrompt != renderedPrompt) { renderedPrompt = renderedEventArgs.RenderedPrompt; if (this._logger.IsEnabled(LogLevel.Trace)) { - this._logger.LogTrace("Rendered prompt changed by handler: {Prompt}", renderedEventArgs.RenderedPrompt); + this._logger.LogTrace("Rendered prompt changed by event handler: {Prompt}", renderedEventArgs.RenderedPrompt); } } +#pragma warning restore CS0618 // Events are deprecated - return (aiService, executionSettings, renderedPrompt, renderedEventArgs); + var renderedContext = kernel.OnPromptRenderedFilter(this, arguments, renderedPrompt); + + if (renderedContext is not null && + !renderedContext.Cancel && + renderedContext.RenderedPrompt != renderedPrompt) + { + renderedPrompt = renderedContext.RenderedPrompt; + + if (this._logger.IsEnabled(LogLevel.Trace)) + { + this._logger.LogTrace("Rendered prompt changed by prompt filter: {Prompt}", renderedContext.RenderedPrompt); + } + } + + return new(aiService, renderedPrompt) + { + ExecutionSettings = executionSettings, + RenderedEventArgs = renderedEventArgs, + RenderedContext = renderedContext + }; } /// Create a random, valid function name. diff --git a/dotnet/src/SemanticKernel.Core/Functions/PromptRenderingResult.cs b/dotnet/src/SemanticKernel.Core/Functions/PromptRenderingResult.cs new file mode 100644 index 000000000000..3a3f8f9e61a5 --- /dev/null +++ b/dotnet/src/SemanticKernel.Core/Functions/PromptRenderingResult.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.SemanticKernel.Services; + +namespace Microsoft.SemanticKernel; + +/// +/// Contains result after prompt rendering process. +/// +internal sealed class PromptRenderingResult +{ + public IAIService AIService { get; set; } + + public string RenderedPrompt { get; set; } + + public PromptExecutionSettings? ExecutionSettings { get; set; } + +#pragma warning disable CS0618 // Events are deprecated + public PromptRenderedEventArgs? RenderedEventArgs { get; set; } +#pragma warning restore CS0618 // Events are deprecated + + public PromptRenderedContext? RenderedContext { get; set; } + + public PromptRenderingResult(IAIService aiService, string renderedPrompt) + { + this.AIService = aiService; + this.RenderedPrompt = renderedPrompt; + } +} diff --git a/dotnet/src/SemanticKernel.UnitTests/Events/FunctionInvokedEventArgsTests.cs b/dotnet/src/SemanticKernel.UnitTests/Events/FunctionInvokedEventArgsTests.cs index f4d1a976e74d..0a338523b9ba 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Events/FunctionInvokedEventArgsTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Events/FunctionInvokedEventArgsTests.cs @@ -6,6 +6,8 @@ namespace SemanticKernel.UnitTests.Events; +#pragma warning disable CS0618 // Events are deprecated + public class FunctionInvokedEventArgsTests { [Fact] diff --git a/dotnet/src/SemanticKernel.UnitTests/Filters/KernelFilterTests.cs b/dotnet/src/SemanticKernel.UnitTests/Filters/KernelFilterTests.cs new file mode 100644 index 000000000000..9c28f9eeece5 --- /dev/null +++ b/dotnet/src/SemanticKernel.UnitTests/Filters/KernelFilterTests.cs @@ -0,0 +1,657 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.TextGeneration; +using Moq; +using Xunit; + +namespace SemanticKernel.UnitTests.Filters; + +public class KernelFilterTests +{ + [Fact] + public async Task PreInvocationFunctionFilterIsTriggeredAsync() + { + // Arrange + var functionInvocations = 0; + var filterInvocations = 0; + var function = KernelFunctionFactory.CreateFromMethod(() => functionInvocations++); + + var kernel = this.GetKernelWithFilters(onFunctionInvoking: (context) => + { + filterInvocations++; + }); + + // Act + var result = await kernel.InvokeAsync(function); + + // Assert + Assert.Equal(1, functionInvocations); + Assert.Equal(1, filterInvocations); + } + + [Fact] + public async Task PreInvocationFunctionFilterChangesArgumentAsync() + { + // Arrange + const string OriginalInput = "OriginalInput"; + const string NewInput = "NewInput"; + + var kernel = this.GetKernelWithFilters(onFunctionInvoking: (context) => + { + context.Arguments["originalInput"] = NewInput; + }); + + var function = KernelFunctionFactory.CreateFromMethod((string originalInput) => originalInput); + + // Act + var result = await kernel.InvokeAsync(function, new() { ["originalInput"] = OriginalInput }); + + // Assert + Assert.Equal(NewInput, result.GetValue()); + } + + [Fact] + public async Task PreInvocationFunctionFilterCancellationWorksCorrectlyAsync() + { + // Arrange + var functionInvocations = 0; + var preFilterInvocations = 0; + var postFilterInvocations = 0; + + var function = KernelFunctionFactory.CreateFromMethod(() => functionInvocations++); + + var kernel = this.GetKernelWithFilters( + onFunctionInvoking: (context) => + { + preFilterInvocations++; + context.Cancel = true; + }, + onFunctionInvoked: (context) => + { + postFilterInvocations++; + }); + + // Act + var exception = await Assert.ThrowsAsync(() => kernel.InvokeAsync(function)); + + // Assert + Assert.Equal(1, preFilterInvocations); + Assert.Equal(0, functionInvocations); + Assert.Equal(0, postFilterInvocations); + Assert.Same(function, exception.Function); + Assert.Null(exception.FunctionResult); + } + + [Fact] + public async Task PreInvocationFunctionFilterCancellationWorksCorrectlyOnStreamingAsync() + { + // Arrange + var functionInvocations = 0; + var filterInvocations = 0; + var function = KernelFunctionFactory.CreateFromMethod(() => functionInvocations++); + + var kernel = this.GetKernelWithFilters(onFunctionInvoking: (context) => + { + filterInvocations++; + context.Cancel = true; + }); + + // Act + IAsyncEnumerable enumerable = function.InvokeStreamingAsync(kernel); + IAsyncEnumerator enumerator = enumerable.GetAsyncEnumerator(); + + Assert.Equal(0, filterInvocations); + + var exception = await Assert.ThrowsAsync(async () => await enumerator.MoveNextAsync()); + + // Assert + Assert.Equal(1, filterInvocations); + Assert.Equal(0, functionInvocations); + Assert.Same(function, exception.Function); + Assert.Same(kernel, exception.Kernel); + Assert.Null(exception.FunctionResult); + } + + [Fact] + public async Task PostInvocationFunctionFilterIsTriggeredAsync() + { + // Arrange + var functionInvocations = 0; + var filterInvocations = 0; + var function = KernelFunctionFactory.CreateFromMethod(() => functionInvocations++); + + var kernel = this.GetKernelWithFilters(onFunctionInvoked: (context) => + { + filterInvocations++; + }); + + // Act + var result = await kernel.InvokeAsync(function); + + // Assert + Assert.Equal(1, functionInvocations); + Assert.Equal(1, filterInvocations); + } + + [Fact] + public async Task PostInvocationFunctionFilterReturnsModifiedResultAsync() + { + // Arrange + const int OriginalResult = 42; + const int NewResult = 84; + + var function = KernelFunctionFactory.CreateFromMethod(() => OriginalResult); + + var kernel = this.GetKernelWithFilters(onFunctionInvoked: (context) => + { + context.SetResultValue(NewResult); + }); + + // Act + var result = await kernel.InvokeAsync(function); + + // Assert + Assert.Equal(NewResult, result.GetValue()); + } + + [Fact] + public async Task PostInvocationFunctionFilterCancellationWorksCorrectlyAsync() + { + // Arrange + const int Result = 42; + + var function = KernelFunctionFactory.CreateFromMethod(() => Result); + var args = new KernelArguments() { { "a", "b" } }; + + var kernel = this.GetKernelWithFilters(onFunctionInvoked: (context) => + { + context.Cancel = true; + }); + + // Act + var exception = await Assert.ThrowsAsync(() => kernel.InvokeAsync(function, args)); + + // Assert + Assert.Same(kernel, exception.Kernel); + Assert.Same(function, exception.Function); + Assert.Same(args, exception.Arguments); + Assert.NotNull(exception.FunctionResult); + Assert.Equal(Result, exception.FunctionResult.GetValue()); + } + + [Fact] + public async Task PostInvocationFunctionFilterCancellationWithModifiedResultAsync() + { + // Arrange + const int OriginalResult = 42; + const int NewResult = 84; + + var function = KernelFunctionFactory.CreateFromMethod(() => OriginalResult); + var args = new KernelArguments() { { "a", "b" } }; + + var kernel = this.GetKernelWithFilters(onFunctionInvoked: (context) => + { + context.SetResultValue(NewResult); + context.Cancel = true; + }); + + // Act + var exception = await Assert.ThrowsAsync(() => kernel.InvokeAsync(function, args)); + + // Assert + Assert.Same(kernel, exception.Kernel); + Assert.Same(function, exception.Function); + Assert.Same(args, exception.Arguments); + Assert.NotNull(exception.FunctionResult); + Assert.Equal(NewResult, exception.FunctionResult.GetValue()); + } + + [Fact] + public async Task PostInvocationFunctionFilterIsNotTriggeredOnStreamingAsync() + { + // Arrange + var functionInvocations = 0; + var preFilterInvocations = 0; + var postFilterInvocations = 0; + + var function = KernelFunctionFactory.CreateFromMethod(() => functionInvocations++); + + var kernel = this.GetKernelWithFilters( + onFunctionInvoking: (context) => + { + preFilterInvocations++; + }, + onFunctionInvoked: (context) => + { + postFilterInvocations++; + }); + + // Act + await foreach (var chunk in kernel.InvokeStreamingAsync(function)) + { + } + + // Assert + Assert.Equal(1, functionInvocations); + Assert.Equal(1, preFilterInvocations); + Assert.Equal(0, postFilterInvocations); + } + + [Fact] + public async Task FunctionFiltersWithPromptsWorkCorrectlyAsync() + { + // Arrange + var preFilterInvocations = 0; + var postFilterInvocations = 0; + var mockTextGeneration = this.GetMockTextGeneration(); + + var kernel = this.GetKernelWithFilters(textGenerationService: mockTextGeneration.Object, + onFunctionInvoking: (context) => + { + preFilterInvocations++; + }, + onFunctionInvoked: (context) => + { + postFilterInvocations++; + }); + + var function = KernelFunctionFactory.CreateFromPrompt("Write a simple phrase about UnitTests"); + + // Act + var result = await kernel.InvokeAsync(function); + + // Assert + Assert.Equal(1, preFilterInvocations); + Assert.Equal(1, postFilterInvocations); + mockTextGeneration.Verify(m => m.GetTextContentsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(1)); + } + + [Fact] + public async Task PromptFiltersAreNotTriggeredForMethodsAsync() + { + // Arrange + var functionInvocations = 0; + var preFilterInvocations = 0; + var postFilterInvocations = 0; + + var function = KernelFunctionFactory.CreateFromMethod(() => functionInvocations++); + + var kernel = this.GetKernelWithFilters( + onPromptRendering: (context) => + { + preFilterInvocations++; + }, + onPromptRendered: (context) => + { + postFilterInvocations++; + }); + + // Act + var result = await kernel.InvokeAsync(function); + + // Assert + Assert.Equal(1, functionInvocations); + Assert.Equal(0, preFilterInvocations); + Assert.Equal(0, postFilterInvocations); + } + + [Fact] + public async Task PromptFiltersAreTriggeredForPromptsAsync() + { + // Arrange + var preFilterInvocations = 0; + var postFilterInvocations = 0; + var mockTextGeneration = this.GetMockTextGeneration(); + + var function = KernelFunctionFactory.CreateFromPrompt("Prompt"); + + var kernel = this.GetKernelWithFilters(textGenerationService: mockTextGeneration.Object, + onPromptRendering: (context) => + { + preFilterInvocations++; + }, + onPromptRendered: (context) => + { + postFilterInvocations++; + }); + + // Act + var result = await kernel.InvokeAsync(function); + + // Assert + Assert.Equal(1, preFilterInvocations); + Assert.Equal(1, postFilterInvocations); + } + + [Fact] + public async Task PromptFiltersAreTriggeredForPromptsStreamingAsync() + { + // Arrange + var preFilterInvocations = 0; + var postFilterInvocations = 0; + var mockTextGeneration = this.GetMockTextGeneration(); + + var function = KernelFunctionFactory.CreateFromPrompt("Prompt"); + + var kernel = this.GetKernelWithFilters(textGenerationService: mockTextGeneration.Object, + onPromptRendering: (context) => + { + preFilterInvocations++; + }, + onPromptRendered: (context) => + { + postFilterInvocations++; + }); + + // Act + await foreach (var chunk in kernel.InvokeStreamingAsync(function)) + { + } + + // Assert + Assert.Equal(1, preFilterInvocations); + Assert.Equal(1, postFilterInvocations); + } + + [Fact] + public async Task PostInvocationPromptFilterChangesRenderedPromptAsync() + { + // Arrange + var mockTextGeneration = this.GetMockTextGeneration(); + var function = KernelFunctionFactory.CreateFromPrompt("Prompt"); + var kernel = this.GetKernelWithFilters(textGenerationService: mockTextGeneration.Object, + onPromptRendered: (context) => + { + context.RenderedPrompt += " - updated from filter"; + }); + + // Act + var result = await kernel.InvokeAsync(function); + + // Assert + mockTextGeneration.Verify(m => m.GetTextContentsAsync("Prompt - updated from filter", It.IsAny(), It.IsAny(), It.IsAny()), Times.Once()); + } + + [Fact] + public async Task PostInvocationPromptFilterCancellationWorksCorrectlyAsync() + { + // Arrange + var mockTextGeneration = this.GetMockTextGeneration(); + var function = KernelFunctionFactory.CreateFromPrompt("Prompt"); + var kernel = this.GetKernelWithFilters(textGenerationService: mockTextGeneration.Object, + onPromptRendered: (context) => + { + context.Cancel = true; + }); + + // Act + var exception = await Assert.ThrowsAsync(() => kernel.InvokeAsync(function)); + + // Assert + Assert.Same(function, exception.Function); + Assert.Same(kernel, exception.Kernel); + Assert.Null(exception.FunctionResult); + } + + [Fact] + public async Task FunctionAndPromptFiltersAreExecutedInCorrectOrderAsync() + { + // Arrange + var builder = Kernel.CreateBuilder(); + var mockTextGeneration = this.GetMockTextGeneration(); + var function = KernelFunctionFactory.CreateFromPrompt("Prompt"); + + var executionOrder = new List(); + + var functionFilter1 = new FakeFunctionFilter( + (context) => executionOrder.Add("FunctionFilter1-Invoking"), + (context) => executionOrder.Add("FunctionFilter1-Invoked")); + + var functionFilter2 = new FakeFunctionFilter( + (context) => executionOrder.Add("FunctionFilter2-Invoking"), + (context) => executionOrder.Add("FunctionFilter2-Invoked")); + + var promptFilter1 = new FakePromptFilter( + (context) => executionOrder.Add("PromptFilter1-Rendering"), + (context) => executionOrder.Add("PromptFilter1-Rendered")); + + var promptFilter2 = new FakePromptFilter( + (context) => executionOrder.Add("PromptFilter2-Rendering"), + (context) => executionOrder.Add("PromptFilter2-Rendered")); + + builder.Services.AddSingleton(functionFilter1); + builder.Services.AddSingleton(functionFilter2); + + builder.Services.AddSingleton(promptFilter1); + builder.Services.AddSingleton(promptFilter2); + + builder.Services.AddSingleton(mockTextGeneration.Object); + + var kernel = builder.Build(); + + // Act + var result = await kernel.InvokeAsync(function); + + // Assert + Assert.Equal("FunctionFilter1-Invoking", executionOrder[0]); + Assert.Equal("FunctionFilter2-Invoking", executionOrder[1]); + Assert.Equal("PromptFilter1-Rendering", executionOrder[2]); + Assert.Equal("PromptFilter2-Rendering", executionOrder[3]); + Assert.Equal("PromptFilter1-Rendered", executionOrder[4]); + Assert.Equal("PromptFilter2-Rendered", executionOrder[5]); + Assert.Equal("FunctionFilter1-Invoked", executionOrder[6]); + Assert.Equal("FunctionFilter2-Invoked", executionOrder[7]); + } + + [Fact] + public async Task MultipleFunctionFiltersCancellationWorksCorrectlyAsync() + { + // Arrange + var functionInvocations = 0; + var filterInvocations = 0; + var function = KernelFunctionFactory.CreateFromMethod(() => functionInvocations++); + + var functionFilter1 = new FakeFunctionFilter(onFunctionInvoking: (context) => + { + filterInvocations++; + context.Cancel = true; + }); + + var functionFilter2 = new FakeFunctionFilter(onFunctionInvoking: (context) => + { + Assert.True(context.Cancel); + + filterInvocations++; + context.Cancel = false; + }); + + var builder = Kernel.CreateBuilder(); + + builder.Services.AddSingleton(functionFilter1); + builder.Services.AddSingleton(functionFilter2); + + var kernel = builder.Build(); + + // Act + var result = await kernel.InvokeAsync(function); + + // Assert + Assert.Equal(1, functionInvocations); + Assert.Equal(2, filterInvocations); + } + + [Fact] + public async Task DifferentWaysOfAddingFunctionFiltersWorkCorrectlyAsync() + { + // Arrange + var function = KernelFunctionFactory.CreateFromMethod(() => "Result"); + var executionOrder = new List(); + + var functionFilter1 = new FakeFunctionFilter((context) => executionOrder.Add("FunctionFilter1-Invoking")); + var functionFilter2 = new FakeFunctionFilter((context) => executionOrder.Add("FunctionFilter2-Invoking")); + + var builder = Kernel.CreateBuilder(); + + // Act + + // Case #1 - Add filter to services + builder.Services.AddSingleton(functionFilter1); + + var kernel = builder.Build(); + + // Case #2 - Add filter to kernel + kernel.FunctionFilters.Add(functionFilter2); + + var result = await kernel.InvokeAsync(function); + + // Assert + Assert.Equal("FunctionFilter1-Invoking", executionOrder[0]); + Assert.Equal("FunctionFilter2-Invoking", executionOrder[1]); + } + + [Fact] + public async Task DifferentWaysOfAddingPromptFiltersWorkCorrectlyAsync() + { + // Arrange + var mockTextGeneration = this.GetMockTextGeneration(); + var function = KernelFunctionFactory.CreateFromPrompt("Prompt"); + var executionOrder = new List(); + + var promptFilter1 = new FakePromptFilter((context) => executionOrder.Add("PromptFilter1-Rendering")); + var promptFilter2 = new FakePromptFilter((context) => executionOrder.Add("PromptFilter2-Rendering")); + + var builder = Kernel.CreateBuilder(); + builder.Services.AddSingleton(mockTextGeneration.Object); + + // Act + // Case #1 - Add filter to services + builder.Services.AddSingleton(promptFilter1); + + var kernel = builder.Build(); + + // Case #2 - Add filter to kernel + kernel.PromptFilters.Add(promptFilter2); + + var result = await kernel.InvokeAsync(function); + + // Assert + Assert.Equal("PromptFilter1-Rendering", executionOrder[0]); + Assert.Equal("PromptFilter2-Rendering", executionOrder[1]); + } + + [Fact] + public async Task InsertFilterInMiddleOfPipelineTriggersFiltersInCorrectOrderAsync() + { + // Arrange + var function = KernelFunctionFactory.CreateFromMethod(() => "Result"); + var executionOrder = new List(); + + var functionFilter1 = new FakeFunctionFilter( + (context) => executionOrder.Add("FunctionFilter1-Invoking"), + (context) => executionOrder.Add("FunctionFilter1-Invoked")); + + var functionFilter2 = new FakeFunctionFilter( + (context) => executionOrder.Add("FunctionFilter2-Invoking"), + (context) => executionOrder.Add("FunctionFilter2-Invoked")); + + var functionFilter3 = new FakeFunctionFilter( + (context) => executionOrder.Add("FunctionFilter3-Invoking"), + (context) => executionOrder.Add("FunctionFilter3-Invoked")); + + var builder = Kernel.CreateBuilder(); + + builder.Services.AddSingleton(functionFilter1); + builder.Services.AddSingleton(functionFilter2); + + var kernel = builder.Build(); + + kernel.FunctionFilters.Insert(1, functionFilter3); + + // Act + var result = await kernel.InvokeAsync(function); + + // Assert + Assert.Equal("FunctionFilter1-Invoking", executionOrder[0]); + Assert.Equal("FunctionFilter3-Invoking", executionOrder[1]); + Assert.Equal("FunctionFilter2-Invoking", executionOrder[2]); + Assert.Equal("FunctionFilter1-Invoked", executionOrder[3]); + Assert.Equal("FunctionFilter3-Invoked", executionOrder[4]); + Assert.Equal("FunctionFilter2-Invoked", executionOrder[5]); + } + + private Kernel GetKernelWithFilters( + Action? onFunctionInvoking = null, + Action? onFunctionInvoked = null, + Action? onPromptRendering = null, + Action? onPromptRendered = null, + ITextGenerationService? textGenerationService = null) + { + var builder = Kernel.CreateBuilder(); + var functionFilter = new FakeFunctionFilter(onFunctionInvoking, onFunctionInvoked); + var promptFilter = new FakePromptFilter(onPromptRendering, onPromptRendered); + + // Add function filter before kernel construction + builder.Services.AddSingleton(functionFilter); + + if (textGenerationService is not null) + { + builder.Services.AddSingleton(textGenerationService); + } + + var kernel = builder.Build(); + + // Add prompt filter after kernel construction + kernel.PromptFilters.Add(promptFilter); + + return kernel; + } + + private Mock GetMockTextGeneration() + { + var mockTextGeneration = new Mock(); + mockTextGeneration + .Setup(m => m.GetTextContentsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(new List { new("result text") }); + + mockTextGeneration + .Setup(s => s.GetStreamingTextContentsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(new List() { new("result chunk") }.ToAsyncEnumerable()); + + return mockTextGeneration; + } + + private sealed class FakeFunctionFilter( + Action? onFunctionInvoking = null, + Action? onFunctionInvoked = null) : IFunctionFilter + { + private readonly Action? _onFunctionInvoking = onFunctionInvoking; + private readonly Action? _onFunctionInvoked = onFunctionInvoked; + + public void OnFunctionInvoked(FunctionInvokedContext context) => + this._onFunctionInvoked?.Invoke(context); + + public void OnFunctionInvoking(FunctionInvokingContext context) => + this._onFunctionInvoking?.Invoke(context); + } + + private sealed class FakePromptFilter( + Action? onPromptRendering = null, + Action? onPromptRendered = null) : IPromptFilter + { + private readonly Action? _onPromptRendering = onPromptRendering; + private readonly Action? _onPromptRendered = onPromptRendered; + + public void OnPromptRendered(PromptRenderedContext context) => + this._onPromptRendered?.Invoke(context); + + public void OnPromptRendering(PromptRenderingContext context) => + this._onPromptRendering?.Invoke(context); + } +} diff --git a/dotnet/src/SemanticKernel.UnitTests/Functions/FunctionFromMethodTests.cs b/dotnet/src/SemanticKernel.UnitTests/Functions/FunctionFromMethodTests.cs index b4626dce5306..445ae9304fb5 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Functions/FunctionFromMethodTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Functions/FunctionFromMethodTests.cs @@ -87,6 +87,7 @@ public async Task InvokeStreamingAsyncOnlySupportsInvokingEventAsync() var invokedCalled = false; var invokingCalled = false; +#pragma warning disable CS0618 // Events are deprecated kernel.FunctionInvoking += (object? sender, FunctionInvokingEventArgs e) => { invokingCalled = true; @@ -97,6 +98,7 @@ public async Task InvokeStreamingAsyncOnlySupportsInvokingEventAsync() { invokedCalled = true; }; +#pragma warning restore CS0618 // Events are deprecated // Act await foreach (var chunk in sut.InvokeStreamingAsync(kernel)) @@ -116,11 +118,14 @@ public async Task InvokeStreamingAsyncInvokingCancelingShouldThrowAsync() var sut = KernelFunctionFactory.CreateFromMethod(() => "any"); bool invokingCalled = false; + +#pragma warning disable CS0618 // Type or member is obsolete kernel.FunctionInvoking += (object? sender, FunctionInvokingEventArgs e) => { invokingCalled = true; e.Cancel = true; }; +#pragma warning restore CS0618 // Type or member is obsolete // Act IAsyncEnumerable enumerable = sut.InvokeStreamingAsync(kernel); @@ -142,11 +147,13 @@ public async Task InvokeStreamingAsyncUsingInvokedEventHasNoEffectAsync() var kernel = new Kernel(); var sut = KernelFunctionFactory.CreateFromMethod(() => "any"); +#pragma warning disable CS0618 // Type or member is obsolete kernel.FunctionInvoked += (object? sender, FunctionInvokedEventArgs e) => { // This will have no effect on streaming e.Cancel = true; }; +#pragma warning restore CS0618 // Type or member is obsolete var chunkCount = 0; diff --git a/dotnet/src/SemanticKernel.UnitTests/Functions/KernelFunctionFromPromptTests.cs b/dotnet/src/SemanticKernel.UnitTests/Functions/KernelFunctionFromPromptTests.cs index a0239238194c..44f785b2878d 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Functions/KernelFunctionFromPromptTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Functions/KernelFunctionFromPromptTests.cs @@ -592,6 +592,7 @@ public async Task InvokeAsyncWithPromptRenderedHooksExecutesModifiedPromptAsync( var mockTextCompletion = new Mock(); mockTextCompletion.Setup(m => m.GetTextContentsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(new List { mockTextContent }); +#pragma warning disable CS0618 // Events are deprecated void MyRenderedHandler(object? sender, PromptRenderedEventArgs e) { e.RenderedPrompt += " USE SHORT, CLEAR, COMPLETE SENTENCES."; @@ -601,6 +602,7 @@ void MyRenderedHandler(object? sender, PromptRenderedEventArgs e) builder.Services.AddKeyedSingleton("service", mockTextCompletion.Object); Kernel kernel = builder.Build(); kernel.PromptRendered += MyRenderedHandler; +#pragma warning restore CS0618 // Events are deprecated KernelFunction function = KernelFunctionFactory.CreateFromPrompt("Prompt"); diff --git a/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs b/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs index 0c75474ffcb5..93f59e9c8588 100644 --- a/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs @@ -16,7 +16,7 @@ using Moq; using Xunit; -// ReSharper disable StringLiteralTypo +#pragma warning disable CS0618 // Events are deprecated namespace SemanticKernel.UnitTests; @@ -583,6 +583,8 @@ public void ItDeepClonesAllRelevantStateInClone() .AddSingleton(new HttpClient()) #pragma warning restore CA2000 .AddSingleton(loggerFactory.Object) + .AddSingleton(new MyFunctionFilter()) + .AddSingleton(new MyPromptFilter()) .BuildServiceProvider(); var plugin = KernelPluginFactory.CreateFromFunctions("plugin1"); var plugins = new KernelPluginCollection() { plugin }; @@ -598,6 +600,7 @@ public void ItDeepClonesAllRelevantStateInClone() Assert.Equal(kernel1.Data["key"], kernel2.Data["key"]); Assert.NotSame(kernel1.Plugins, kernel2.Plugins); Assert.Equal(kernel1.Plugins, kernel2.Plugins); + this.AssertFilters(kernel1, kernel2); // Minimally configured kernel Kernel kernel3 = new(); @@ -609,6 +612,7 @@ public void ItDeepClonesAllRelevantStateInClone() Assert.Empty(kernel4.Data); Assert.NotSame(kernel1.Plugins, kernel2.Plugins); Assert.Empty(kernel4.Plugins); + this.AssertFilters(kernel3, kernel4); } [Fact] @@ -654,6 +658,29 @@ private Mock SetupStreamingMocks(params StreamingTextCon return mockTextCompletion; } + private void AssertFilters(Kernel kernel1, Kernel kernel2) + { + var functionFilters1 = kernel1.GetAllServices().ToArray(); + var promptFilters1 = kernel1.GetAllServices().ToArray(); + + var functionFilters2 = kernel2.GetAllServices().ToArray(); + var promptFilters2 = kernel2.GetAllServices().ToArray(); + + Assert.Equal(functionFilters1.Length, functionFilters2.Length); + + for (var i = 0; i < functionFilters1.Length; i++) + { + Assert.Same(functionFilters1[i], functionFilters2[i]); + } + + Assert.Equal(promptFilters1.Length, promptFilters2.Length); + + for (var i = 0; i < promptFilters1.Length; i++) + { + Assert.Same(promptFilters1[i], promptFilters2[i]); + } + } + public class MyPlugin { [KernelFunction, Description("Return any value.")] @@ -675,4 +702,22 @@ public async Task ReadFunctionCollectionAsync(Kernel kernel) Assert.NotNull(kernel.Plugins); } } + + private sealed class MyFunctionFilter : IFunctionFilter + { + public void OnFunctionInvoked(FunctionInvokedContext context) + { } + + public void OnFunctionInvoking(FunctionInvokingContext context) + { } + } + + private sealed class MyPromptFilter : IPromptFilter + { + public void OnPromptRendered(PromptRenderedContext context) + { } + + public void OnPromptRendering(PromptRenderingContext context) + { } + } } diff --git a/dotnet/src/SemanticKernel.UnitTests/TemplateEngine/Blocks/CodeBlockTests.cs b/dotnet/src/SemanticKernel.UnitTests/TemplateEngine/Blocks/CodeBlockTests.cs index b2012b15488f..e9beab7c851a 100644 --- a/dotnet/src/SemanticKernel.UnitTests/TemplateEngine/Blocks/CodeBlockTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/TemplateEngine/Blocks/CodeBlockTests.cs @@ -400,6 +400,7 @@ public async Task ItCallsPromptFunctionWithPositionalTargetFirstArgumentRegardle } ); +#pragma warning disable CS0618 // Events are deprecated kernel.PromptRendering += (object? sender, PromptRenderingEventArgs e) => { Assert.Equal(FooValue, e.Arguments[parameterName]); @@ -409,6 +410,7 @@ public async Task ItCallsPromptFunctionWithPositionalTargetFirstArgumentRegardle { Assert.Equal(FooValue, e.Arguments[parameterName]); }; +#pragma warning restore CS0618 // Events are deprecated var codeBlock = new CodeBlock(blockList, ""); await codeBlock.RenderCodeAsync(kernel); @@ -444,6 +446,7 @@ public async Task ItCallsPromptFunctionMatchArgumentWithNamedArgsAsync() } ); +#pragma warning disable CS0618 // Events are deprecated kernel.PromptRendering += (object? sender, PromptRenderingEventArgs e) => { Assert.Equal(FooValue, e.Arguments["foo"]); @@ -455,6 +458,7 @@ public async Task ItCallsPromptFunctionMatchArgumentWithNamedArgsAsync() Assert.Equal(FooValue, e.Arguments["foo"]); Assert.Equal(FooValue, e.Arguments["x11"]); }; +#pragma warning restore CS0618 // Events are deprecated var codeBlock = new CodeBlock(blockList, ""); await codeBlock.RenderCodeAsync(kernel, arguments); diff --git a/python/.cspell.json b/python/.cspell.json index 341016cf6035..84f8f4629e52 100644 --- a/python/.cspell.json +++ b/python/.cspell.json @@ -38,7 +38,7 @@ "OPENAI", "pydantic", "retrywrites", - "skfunction", + "kernelfunction", "skprompt", "templating", "vectordb" diff --git a/python/DEV_SETUP.md b/python/DEV_SETUP.md index b851b3b2b8dc..2103c0e06148 100644 --- a/python/DEV_SETUP.md +++ b/python/DEV_SETUP.md @@ -170,7 +170,7 @@ from this field is sufficient to have these types of classes as valid Pydantic f any class using them as attributes to be serialized. ```python -from semantic_kernel.sk_pydantic import PydanticField +from semantic_kernel.kernel_pydantic import PydanticField class B(PydanticField): ... # correct, B is still an ABC because PydanticField subclasses ABC class B(PydanticField, ABC): ... # Also correct @@ -223,13 +223,13 @@ class A: self.d = d ``` -You would convert this to a Pydantic class by subclassing from the `SKBaseModel` class. +You would convert this to a Pydantic class by subclassing from the `KernelBaseModel` class. ```python from pydantic import Field -from semantic_kernel.sk_pydantic import SKBaseModel +from semantic_kernel.kernel_pydantic import KernelBaseModel -class A(SKBaseModel): +class A(KernelBaseModel): # The notation for the fields is similar to dataclasses. a: int b: float @@ -255,14 +255,14 @@ class A: self.c = c ``` -You can uses the `SKGenericModel` to convert these to pydantic serializable classes. +You can use the `KernelBaseModel` to convert these to pydantic serializable classes. ```python from typing import Generic -from semantic_kernel.sk_pydantic import SKGenericModel +from semantic_kernel.kernel_pydantic import KernelBaseModel -class A(SKGenericModel, Generic[T1, T2]): +class A(KernelBaseModel, Generic[T1, T2]): # T1 and T2 must be specified in the Generic argument otherwise, pydantic will # NOT be able to serialize this class a: int diff --git a/python/notebooks/00-getting-started.ipynb b/python/notebooks/00-getting-started.ipynb index 34e50200bf9c..ac935ec9f5f3 100644 --- a/python/notebooks/00-getting-started.ipynb +++ b/python/notebooks/00-getting-started.ipynb @@ -16,7 +16,7 @@ "metadata": {}, "outputs": [], "source": [ - "!python -m pip install semantic-kernel==0.4.5.dev0" + "!python -m pip install semantic-kernel==0.4.6.dev0" ] }, { diff --git a/python/notebooks/01-basic-loading-the-kernel.ipynb b/python/notebooks/01-basic-loading-the-kernel.ipynb index 9d5be85de411..25eee18a15e5 100644 --- a/python/notebooks/01-basic-loading-the-kernel.ipynb +++ b/python/notebooks/01-basic-loading-the-kernel.ipynb @@ -25,7 +25,7 @@ "metadata": {}, "outputs": [], "source": [ - "!python -m pip install semantic-kernel==0.4.5.dev0" + "!python -m pip install semantic-kernel==0.4.6.dev0" ] }, { diff --git a/python/notebooks/02-running-prompts-from-file.ipynb b/python/notebooks/02-running-prompts-from-file.ipynb index 23b3185b3937..7c76d1d62c90 100644 --- a/python/notebooks/02-running-prompts-from-file.ipynb +++ b/python/notebooks/02-running-prompts-from-file.ipynb @@ -89,7 +89,7 @@ "metadata": {}, "outputs": [], "source": [ - "!python -m pip install semantic-kernel==0.4.5.dev0" + "!python -m pip install semantic-kernel==0.4.6.dev0" ] }, { diff --git a/python/notebooks/03-semantic-function-inline.ipynb b/python/notebooks/03-semantic-function-inline.ipynb index 4e58d805a8e3..56464f0a03e7 100644 --- a/python/notebooks/03-semantic-function-inline.ipynb +++ b/python/notebooks/03-semantic-function-inline.ipynb @@ -55,7 +55,7 @@ "metadata": {}, "outputs": [], "source": [ - "!python -m pip install semantic-kernel==0.4.5.dev0" + "!python -m pip install semantic-kernel==0.4.6.dev0" ] }, { diff --git a/python/notebooks/04-context-variables-chat.ipynb b/python/notebooks/04-context-variables-chat.ipynb index 3a629f375956..d7dca0dc294b 100644 --- a/python/notebooks/04-context-variables-chat.ipynb +++ b/python/notebooks/04-context-variables-chat.ipynb @@ -26,7 +26,7 @@ "metadata": {}, "outputs": [], "source": [ - "!python -m pip install semantic-kernel==0.4.5.dev0" + "!python -m pip install semantic-kernel==0.4.6.dev0" ] }, { diff --git a/python/notebooks/05-using-the-planner.ipynb b/python/notebooks/05-using-the-planner.ipynb index 8aefbbab3cc4..16959ba01580 100644 --- a/python/notebooks/05-using-the-planner.ipynb +++ b/python/notebooks/05-using-the-planner.ipynb @@ -11,9 +11,9 @@ "\n", "It makes use of the collection of native and semantic functions that have been registered to the kernel and using AI, will formulate a plan to execute the given ask.\n", "\n", - "From our own testing, planner works best with more powerful models like `gpt4` but sometimes you might get working plans with cheaper models like `gpt-35-turbo`. We encourage you to implement your own versions of the planner and use different models that fit your user needs. \n", + "From our own testing, planner works best with more powerful models like `gpt4` but sometimes you might get working plans with cheaper models like `gpt-35-turbo`. We encourage you to implement your own versions of the planner and use different models that fit your user needs.\n", "\n", - "Read more about planner [here](https://aka.ms/sk/concepts/planner)" + "Read more about planner [here](https://aka.ms/sk/concepts/planner)\n" ] }, { @@ -23,7 +23,7 @@ "metadata": {}, "outputs": [], "source": [ - "!python -m pip install semantic-kernel==0.4.5.dev0" + "!python -m pip install semantic-kernel==0.4.6.dev0" ] }, { @@ -63,7 +63,7 @@ "id": "4ff28070", "metadata": {}, "source": [ - "## It all begins with an ask" + "## It all begins with an ask\n" ] }, { @@ -84,9 +84,10 @@ "metadata": {}, "source": [ "### Providing plugins to the planner\n", - "The planner needs to know what plugins are available to it. Here we'll give it access to the `SummarizePlugin` and `WriterPlugin` we have defined on disk. This will include many semantic functions, of which the planner will intelligently choose a subset. \n", "\n", - "You can also include native functions as well. Here we'll add the TextPlugin." + "The planner needs to know what plugins are available to it. Here we'll give it access to the `SummarizePlugin` and `WriterPlugin` we have defined on disk. This will include many semantic functions, of which the planner will intelligently choose a subset.\n", + "\n", + "You can also include native functions as well. Here we'll add the TextPlugin.\n" ] }, { @@ -109,7 +110,7 @@ "id": "deff5675", "metadata": {}, "source": [ - "Define your ASK. What do you want the Kernel to do?" + "Define your ASK. What do you want the Kernel to do?\n" ] }, { @@ -117,7 +118,7 @@ "id": "eee6fe7b", "metadata": {}, "source": [ - "# Basic Planner" + "# Basic Planner\n" ] }, { @@ -125,7 +126,7 @@ "id": "590a22f2", "metadata": {}, "source": [ - " Let's start by taking a look at a basic planner. The `BasicPlanner` produces a JSON-based plan that aims to solve the provided ask sequentially and evaluated in order." + "Let's start by taking a look at a basic planner. The `BasicPlanner` produces a JSON-based plan that aims to solve the provided ask sequentially and evaluated in order.\n" ] }, { @@ -167,7 +168,7 @@ "source": [ "You can see that the Planner took my ask and converted it into an JSON-based plan detailing how the AI would go about solving this task, making use of the plugins that the Kernel has available to it.\n", "\n", - "As you can see in the above plan, the AI has determined which functions to call in order to fulfill the user ask. The output of each step of the plan becomes the input to the next function." + "As you can see in the above plan, the AI has determined which functions to call in order to fulfill the user ask. The output of each step of the plan becomes the input to the next function.\n" ] }, { @@ -175,7 +176,7 @@ "id": "cd4df0c2", "metadata": {}, "source": [ - "Let's also define an inline plugin and have it be available to the Planner. Be sure to give it a function name and plugin name." + "Let's also define an inline plugin and have it be available to the Planner. Be sure to give it a function name and plugin name.\n" ] }, { @@ -204,7 +205,7 @@ "id": "5057cf9b", "metadata": {}, "source": [ - "Let's update our ask using this new plugin" + "Let's update our ask using this new plugin\n" ] }, { @@ -237,7 +238,7 @@ "id": "b67a052e", "metadata": {}, "source": [ - "### Executing the plan" + "### Executing the plan\n" ] }, { @@ -245,7 +246,7 @@ "id": "3b839c90", "metadata": {}, "source": [ - "Now that we have a plan, let's try to execute it! The Planner has a function called `execute_plan`." + "Now that we have a plan, let's try to execute it! The Planner has a function called `execute_plan`.\n" ] }, { @@ -273,7 +274,7 @@ "id": "e8a9b6b7", "metadata": {}, "source": [ - "# The Plan Object Model" + "# The Plan Object Model\n" ] }, { @@ -283,7 +284,7 @@ "source": [ "To build more advanced planners, we need to introduce a proper Plan object that can contain all the necessary state and information needed for high quality plans.\n", "\n", - "To see what that object model is, look at (https://github.com/microsoft/semantic-kernel/blob/main/python/semantic_kernel/planning/plan.py)" + "To see what that object model is, look at (https://github.com/microsoft/semantic-kernel/blob/main/python/semantic_kernel/planning/plan.py)\n" ] }, { @@ -291,7 +292,7 @@ "id": "0a0cb2a2", "metadata": {}, "source": [ - "# Sequential Planner" + "# Sequential Planner\n" ] }, { @@ -299,7 +300,7 @@ "id": "a1c66d83", "metadata": {}, "source": [ - "The sequential planner is an XML-based step-by-step planner. You can see the prompt used for it here (https://github.com/microsoft/semantic-kernel/blob/main/python/semantic_kernel/planning/sequential_planner/Plugins/SequentialPlanning/skprompt.txt)" + "The sequential planner is an XML-based step-by-step planner. You can see the prompt used for it here (https://github.com/microsoft/semantic-kernel/blob/main/python/semantic_kernel/planning/sequential_planner/Plugins/SequentialPlanning/skprompt.txt)\n" ] }, { @@ -329,7 +330,7 @@ "id": "ee2f462b", "metadata": {}, "source": [ - "To see the steps that the Sequential Planner will take, we can iterate over them and print their descriptions" + "To see the steps that the Sequential Planner will take, we can iterate over them and print their descriptions\n" ] }, { @@ -348,7 +349,7 @@ "id": "4db5f844", "metadata": {}, "source": [ - "Let's ask the sequential planner to execute the plan." + "Let's ask the sequential planner to execute the plan.\n" ] }, { @@ -376,7 +377,7 @@ "id": "d6487c75", "metadata": {}, "source": [ - "# Action Planner" + "# Action Planner\n" ] }, { @@ -384,7 +385,7 @@ "id": "b045e26b", "metadata": {}, "source": [ - "The action planner takes in a list of functions and the goal, and outputs a **single** function to use that is appropriate to meet that goal." + "The action planner takes in a list of functions and the goal, and outputs a **single** function to use that is appropriate to meet that goal.\n" ] }, { @@ -404,7 +405,7 @@ "id": "53b1f296", "metadata": {}, "source": [ - "Let's add more plugins to the kernel" + "Let's add more plugins to the kernel\n" ] }, { @@ -472,7 +473,7 @@ "id": "789b651a", "metadata": {}, "source": [ - "# Stepwise Planner" + "# Stepwise Planner\n" ] }, { @@ -482,7 +483,7 @@ "source": [ "Stepwise Planner is based off the paper from MRKL (Modular Reasoning, Knowledge and Language) and is similar to other papers like ReACT (Reasoning and Acting in Language Models). At the core, the stepwise planner allows for the AI to form \"thoughts\" and \"observations\" and execute actions based off those to achieve a user's goal. This continues until all required functions are complete and a final output is generated.\n", "\n", - "See a video walkthrough of Stepwise Planner [here.](https://youtu.be/DG_Ge1v0c4Q?si=T1CHaAm1vV0mWRHu)" + "See a video walkthrough of Stepwise Planner [here.](https://youtu.be/DG_Ge1v0c4Q?si=T1CHaAm1vV0mWRHu)\n" ] }, { @@ -507,7 +508,7 @@ "\n", "Make sure you have a Bing Search API key in your `.env` file\n", "\n", - "(https://www.microsoft.com/en-us/bing/apis/bing-web-search-api)" + "(https://www.microsoft.com/en-us/bing/apis/bing-web-search-api)\n" ] }, { @@ -522,21 +523,21 @@ " A search engine plugin.\n", " \"\"\"\n", "\n", - " from semantic_kernel.orchestration.sk_context import SKContext\n", + " from semantic_kernel.orchestration.kernel_context import KernelContext\n", " from semantic_kernel.plugin_definition import (\n", - " sk_function,\n", - " sk_function_context_parameter,\n", + " kernel_function,\n", + " kernel_function_context_parameter,\n", " )\n", "\n", " def __init__(self, connector) -> None:\n", " self._connector = connector\n", "\n", - " @sk_function(description=\"Performs a web search for a given query\", name=\"searchAsync\")\n", - " @sk_function_context_parameter(\n", + " @kernel_function(description=\"Performs a web search for a given query\", name=\"searchAsync\")\n", + " @kernel_function_context_parameter(\n", " name=\"query\",\n", " description=\"The search query\",\n", " )\n", - " async def search_async(self, query: str, context: SKContext) -> str:\n", + " async def search_async(self, query: str, context: KernelContext) -> str:\n", " query = query or context.variables.get(\"query\")[1]\n", " result = await self._connector.search_async(query, num_results=5, offset=0)\n", " return str(result)" @@ -561,7 +562,7 @@ "id": "effdf3ab", "metadata": {}, "source": [ - "Let's also add a couple more plugins" + "Let's also add a couple more plugins\n" ] }, { @@ -593,7 +594,7 @@ "id": "50699ec3", "metadata": {}, "source": [ - "Now let's do a more complicated ask that will require planner to make a call to Bing to get the latest information." + "Now let's do a more complicated ask that will require planner to make a call to Bing to get the latest information.\n" ] }, { @@ -633,7 +634,7 @@ "id": "cb40370d", "metadata": {}, "source": [ - "Let's see the steps that the AI took to get to the answer." + "Let's see the steps that the AI took to get to the answer.\n" ] }, { diff --git a/python/notebooks/06-memory-and-embeddings.ipynb b/python/notebooks/06-memory-and-embeddings.ipynb index add60b514495..4620432dc70d 100644 --- a/python/notebooks/06-memory-and-embeddings.ipynb +++ b/python/notebooks/06-memory-and-embeddings.ipynb @@ -9,16 +9,16 @@ "# Building Semantic Memory with Embeddings\n", "\n", "So far, we've mostly been treating the kernel as a stateless orchestration engine.\n", - "We send text into a model API and receive text out. \n", + "We send text into a model API and receive text out.\n", "\n", "In a [previous notebook](04-context-variables-chat.ipynb), we used `context variables` to pass in additional\n", - "text into prompts to enrich them with more context. This allowed us to create a basic chat experience. \n", + "text into prompts to enrich them with more context. This allowed us to create a basic chat experience.\n", "\n", "However, if you solely relied on context variables, you would quickly realize that eventually your prompt\n", "would grow so large that you would run into the model's token limit. What we need is a way to persist state\n", - "and build both short-term and long-term memory to empower even more intelligent applications. \n", + "and build both short-term and long-term memory to empower even more intelligent applications.\n", "\n", - "To do this, we dive into the key concept of `Semantic Memory` in the Semantic Kernel. " + "To do this, we dive into the key concept of `Semantic Memory` in the Semantic Kernel.\n" ] }, { @@ -28,7 +28,7 @@ "metadata": {}, "outputs": [], "source": [ - "!python -m pip install semantic-kernel==0.4.5.dev0" + "!python -m pip install semantic-kernel==0.4.6.dev0" ] }, { @@ -58,7 +58,7 @@ "In order to use memory, we need to instantiate the Kernel with a Memory Storage\n", "and an Embedding service. In this example, we make use of the `VolatileMemoryStore` which can be thought of as a temporary in-memory storage. This memory is not written to disk and is only available during the app session.\n", "\n", - "When developing your app you will have the option to plug in persistent storage like Azure AI Search, Azure Cosmos Db, PostgreSQL, SQLite, etc. Semantic Memory allows also to index external data sources, without duplicating all the information as you will see further down in this notebook." + "When developing your app you will have the option to plug in persistent storage like Azure AI Search, Azure Cosmos Db, PostgreSQL, SQLite, etc. Semantic Memory allows also to index external data sources, without duplicating all the information as you will see further down in this notebook.\n" ] }, { @@ -100,7 +100,7 @@ "source": [ "At its core, Semantic Memory is a set of data structures that allow you to store the meaning of text that come from different data sources, and optionally to store the source text too. These texts can be from the web, e-mail providers, chats, a database, or from your local directory, and are hooked up to the Semantic Kernel through data source connectors.\n", "\n", - "The texts are embedded or compressed into a vector of floats representing mathematically the texts' contents and meaning. You can read more about embeddings [here](https://aka.ms/sk/embeddings)." + "The texts are embedded or compressed into a vector of floats representing mathematically the texts' contents and meaning. You can read more about embeddings [here](https://aka.ms/sk/embeddings).\n" ] }, { @@ -110,7 +110,8 @@ "metadata": {}, "source": [ "### Manually adding memories\n", - "Let's create some initial memories \"About Me\". We can add memories to our `VolatileMemoryStore` by using `SaveInformationAsync`" + "\n", + "Let's create some initial memories \"About Me\". We can add memories to our `VolatileMemoryStore` by using `SaveInformationAsync`\n" ] }, { @@ -152,7 +153,7 @@ "id": "2calf857", "metadata": {}, "source": [ - "Let's try searching the memory:" + "Let's try searching the memory:\n" ] }, { @@ -193,7 +194,7 @@ "metadata": {}, "source": [ "Let's now revisit the our chat sample from the [previous notebook](04-context-variables-chat.ipynb).\n", - "If you remember, we used context variables to fill the prompt with a `history` that continuously got populated as we chatted with the bot. Let's add also memory to it!" + "If you remember, we used context variables to fill the prompt with a `history` that continuously got populated as we chatted with the bot. Let's add also memory to it!\n" ] }, { @@ -205,7 +206,7 @@ "This is done by using the `TextMemoryPlugin` which exposes the `recall` native function.\n", "\n", "`recall` takes an input ask and performs a similarity search on the contents that have\n", - "been embedded in the Memory Store and returns the most relevant memory. " + "been embedded in the Memory Store and returns the most relevant memory.\n" ] }, { @@ -217,7 +218,7 @@ "source": [ "async def setup_chat_with_memory(\n", " kernel: sk.Kernel,\n", - ") -> Tuple[sk.SKFunctionBase, sk.SKContext]:\n", + ") -> Tuple[sk.KernelFunctionBase, sk.KernelContext]:\n", " sk_prompt = \"\"\"\n", " ChatBot can have a conversation with you about any topic.\n", " It can give explicit instructions or say 'I don't know' if\n", @@ -258,7 +259,7 @@ "id": "1ac62457", "metadata": {}, "source": [ - "The `RelevanceParam` is used in memory search and is a measure of the relevance score from 0.0 to 1.0, where 1.0 means a perfect match. We encourage users to experiment with different values." + "The `RelevanceParam` is used in memory search and is a measure of the relevance score from 0.0 to 1.0, where 1.0 means a perfect match. We encourage users to experiment with different values.\n" ] }, { @@ -267,7 +268,7 @@ "id": "645b55a1", "metadata": {}, "source": [ - "Now that we've included our memories, let's chat!" + "Now that we've included our memories, let's chat!\n" ] }, { @@ -277,7 +278,7 @@ "metadata": {}, "outputs": [], "source": [ - "async def chat(kernel: sk.Kernel, chat_func: sk.SKFunctionBase, context: sk.SKContext) -> bool:\n", + "async def chat(kernel: sk.Kernel, chat_func: sk.KernelFunctionBase, context: sk.KernelContext) -> bool:\n", " try:\n", " user_input = input(\"User:> \")\n", " context[\"user_input\"] = user_input\n", @@ -332,7 +333,7 @@ "\n", "Many times in your applications you'll want to bring in external documents into your memory. Let's see how we can do this using our VolatileMemoryStore.\n", "\n", - "Let's first get some data using some of the links in the Semantic Kernel repo." + "Let's first get some data using some of the links in the Semantic Kernel repo.\n" ] }, { @@ -366,7 +367,7 @@ "id": "75f3ea5e", "metadata": {}, "source": [ - "Now let's add these files to our VolatileMemoryStore using `SaveReferenceAsync`. We'll separate these memories from the chat memories by putting them in a different collection." + "Now let's add these files to our VolatileMemoryStore using `SaveReferenceAsync`. We'll separate these memories from the chat memories by putting them in a different collection.\n" ] }, { @@ -448,7 +449,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The implementation of Semantic Kernel allows to easily swap memory store for another. Here, we will re-use the functions we initially created for `VolatileMemoryStore` with our new external Vector Store leveraging Azure AI Search" + "The implementation of Semantic Kernel allows to easily swap memory store for another. Here, we will re-use the functions we initially created for `VolatileMemoryStore` with our new external Vector Store leveraging Azure AI Search\n" ] }, { @@ -464,7 +465,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can see that our function created an \"About Me\" index and that our five pieces of information have been indexed (note that it can take a few minutes for the UI to reflect the document count and storage size)." + "We can see that our function created an \"About Me\" index and that our five pieces of information have been indexed (note that it can take a few minutes for the UI to reflect the document count and storage size).\n" ] }, { @@ -476,14 +477,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "![image.png](attachment:image.png)" + "![image.png](attachment:image.png)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "And we can see that embeddings have been conveniently created to allow for semantic search." + "And we can see that embeddings have been conveniently created to allow for semantic search.\n" ] }, { @@ -495,14 +496,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "![image.png](attachment:image.png)" + "![image.png](attachment:image.png)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Let's now try to query from Azure AI Search!" + "Let's now try to query from Azure AI Search!\n" ] }, { @@ -518,7 +519,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We have laid the foundation which will allow us to store an arbitrary amount of data in an external Vector Store above and beyond what could fit in memory at the expense of a little more latency." + "We have laid the foundation which will allow us to store an arbitrary amount of data in an external Vector Store above and beyond what could fit in memory at the expense of a little more latency.\n" ] } ], @@ -538,7 +539,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.9.13" } }, "nbformat": 4, diff --git a/python/notebooks/07-hugging-face-for-plugins.ipynb b/python/notebooks/07-hugging-face-for-plugins.ipynb index b1b96fb5f70c..9c57d94c7d20 100644 --- a/python/notebooks/07-hugging-face-for-plugins.ipynb +++ b/python/notebooks/07-hugging-face-for-plugins.ipynb @@ -20,7 +20,7 @@ "metadata": {}, "outputs": [], "source": [ - "!python -m pip install semantic-kernel==0.4.5.dev0\n", + "!python -m pip install semantic-kernel==0.4.6.dev0\n", "\n", "# Note that additional dependencies are required for the Hugging Face connectors:\n", "!python -m pip install torch==2.0.0\n", diff --git a/python/notebooks/08-native-function-inline.ipynb b/python/notebooks/08-native-function-inline.ipynb index 67c00ca96dad..87d37d0f2acc 100644 --- a/python/notebooks/08-native-function-inline.ipynb +++ b/python/notebooks/08-native-function-inline.ipynb @@ -6,7 +6,7 @@ "id": "3c93ac5b", "metadata": {}, "source": [ - "# Running Native Functions" + "# Running Native Functions\n" ] }, { @@ -21,13 +21,13 @@ "\n", "This can be useful in a few scenarios:\n", "\n", - "* Writing logic around how to run a prompt that changes the prompt's outcome.\n", - "* Using external data sources to gather data to concatenate into your prompt.\n", - "* Validating user input data prior to sending it to the LLM prompt.\n", + "- Writing logic around how to run a prompt that changes the prompt's outcome.\n", + "- Using external data sources to gather data to concatenate into your prompt.\n", + "- Validating user input data prior to sending it to the LLM prompt.\n", "\n", "Native functions are defined using standard Python code. The structure is simple, but not well documented at this point.\n", "\n", - "The following examples are intended to help guide new users towards successful native & semantic function use with the SK Python framework." + "The following examples are intended to help guide new users towards successful native & semantic function use with the SK Python framework.\n" ] }, { @@ -36,7 +36,7 @@ "id": "d90b0c13", "metadata": {}, "source": [ - "Prepare a semantic kernel instance first, loading also the AI service settings defined in the [Setup notebook](00-getting-started.ipynb):" + "Prepare a semantic kernel instance first, loading also the AI service settings defined in the [Setup notebook](00-getting-started.ipynb):\n" ] }, { @@ -46,7 +46,7 @@ "metadata": {}, "outputs": [], "source": [ - "!python -m pip install semantic-kernel==0.4.5.dev0" + "!python -m pip install semantic-kernel==0.4.6.dev0" ] }, { @@ -85,7 +85,7 @@ "id": "186767f8", "metadata": {}, "source": [ - "Let's create a **native** function that gives us a random number between 3 and a user input as the upper limit. We'll use this number to create 3-x paragraphs of text when passed to a semantic function." + "Let's create a **native** function that gives us a random number between 3 and a user input as the upper limit. We'll use this number to create 3-x paragraphs of text when passed to a semantic function.\n" ] }, { @@ -94,7 +94,7 @@ "id": "589733c5", "metadata": {}, "source": [ - "First, let's create our native function." + "First, let's create our native function.\n" ] }, { @@ -105,7 +105,7 @@ "outputs": [], "source": [ "import random\n", - "from semantic_kernel.plugin_definition import sk_function\n", + "from semantic_kernel.plugin_definition import kernel_function\n", "\n", "\n", "class GenerateNumberPlugin:\n", @@ -113,7 +113,7 @@ " Description: Generate a number between 3-x.\n", " \"\"\"\n", "\n", - " @sk_function(\n", + " @kernel_function(\n", " description=\"Generate a random number between 3-x\",\n", " name=\"GenerateNumberThreeOrHigher\",\n", " )\n", @@ -140,7 +140,7 @@ "id": "f26b90c4", "metadata": {}, "source": [ - "Next, let's create a semantic function that accepts a number as `{{$input}}` and generates that number of paragraphs about two Corgis on an adventure. `$input` is a default variable semantic functions can use. " + "Next, let's create a semantic function that accepts a number as `{{$input}}` and generates that number of paragraphs about two Corgis on an adventure. `$input` is a default variable semantic functions can use.\n" ] }, { @@ -215,11 +215,11 @@ "source": [ "## Context Variables\n", "\n", - "That works! But let's expand on our example to make it more generic. \n", + "That works! But let's expand on our example to make it more generic.\n", "\n", "For the native function, we'll introduce the lower limit variable. This means that a user will input two numbers and the number generator function will pick a number between the first and second input.\n", "\n", - "We'll make use of the `semantic_kernel.ContextVariables` class to do hold these variables." + "We'll make use of the `semantic_kernel.ContextVariables` class to do hold these variables.\n" ] }, { @@ -258,7 +258,7 @@ "id": "091f45e4", "metadata": {}, "source": [ - "Let's start with the native function. Notice that we're also adding `@sk_function_context_parameter` decorators to the function here to provide context about what variables need to be provided to the function, and any defaults for those inputs. Using the `@sk_function_context_parameter` decorator provides the name, description and default values for a function's inputs to the [planner.](./05-using-the-planner.ipynb)" + "Let's start with the native function. Notice that we're also adding `@kernel_function_context_parameter` decorators to the function here to provide context about what variables need to be provided to the function, and any defaults for those inputs. Using the `@kernel_function_context_parameter` decorator provides the name, description and default values for a function's inputs to the [planner.](./05-using-the-planner.ipynb)\n" ] }, { @@ -269,8 +269,8 @@ "outputs": [], "source": [ "import random\n", - "from semantic_kernel.plugin_definition import sk_function, sk_function_context_parameter\n", - "from semantic_kernel import SKContext\n", + "from semantic_kernel.plugin_definition import kernel_function, kernel_function_context_parameter\n", + "from semantic_kernel import KernelContext\n", "\n", "\n", "class GenerateNumberPlugin:\n", @@ -278,13 +278,13 @@ " Description: Generate a number between a min and a max.\n", " \"\"\"\n", "\n", - " @sk_function(\n", + " @kernel_function(\n", " description=\"Generate a random number between min and max\",\n", " name=\"GenerateNumber\",\n", " )\n", - " @sk_function_context_parameter(name=\"min\", description=\"Minimum number of paragraphs.\")\n", - " @sk_function_context_parameter(name=\"max\", description=\"Maximum number of paragraphs.\", default_value=\"10\")\n", - " def generate_number(self, context: SKContext) -> str:\n", + " @kernel_function_context_parameter(name=\"min\", description=\"Minimum number of paragraphs.\")\n", + " @kernel_function_context_parameter(name=\"max\", description=\"Maximum number of paragraphs.\", default_value=\"10\")\n", + " def generate_number(self, context: KernelContext) -> str:\n", " \"\"\"\n", " Generate a number between min-max\n", " Example:\n", @@ -319,7 +319,7 @@ "id": "6ad068d6", "metadata": {}, "source": [ - "Now let's also allow the semantic function to take in additional arguments. In this case, we're going to allow the our CorgiStory function to be written in a specified language. We'll need to provide a `paragraph_count` and a `language`." + "Now let's also allow the semantic function to take in additional arguments. In this case, we're going to allow the our CorgiStory function to be written in a specified language. We'll need to provide a `paragraph_count` and a `language`.\n" ] }, { @@ -356,7 +356,7 @@ "id": "fdce1872", "metadata": {}, "source": [ - "Now we can call this using our `invoke` function by passing in our `context_variables` in the `variables` parameter. " + "Now we can call this using our `invoke` function by passing in our `context_variables` in the `variables` parameter.\n" ] }, { @@ -375,7 +375,7 @@ "id": "c8778bad", "metadata": {}, "source": [ - "Let's add a paragraph count to our context variables " + "Let's add a paragraph count to our context variables\n" ] }, { @@ -429,7 +429,7 @@ "\n", "We will make our CorgiStory semantic function call a native function `GenerateNames` which will return names for our Corgi characters.\n", "\n", - "We do this using the syntax `{{plugin_name.function_name}}`. You can read more about our prompte templating syntax [here](../../../docs/PROMPT_TEMPLATE_LANGUAGE.md). " + "We do this using the syntax `{{plugin_name.function_name}}`. You can read more about our prompte templating syntax [here](../../../docs/PROMPT_TEMPLATE_LANGUAGE.md).\n" ] }, { @@ -440,7 +440,7 @@ "outputs": [], "source": [ "import random\n", - "from semantic_kernel.plugin_definition import sk_function\n", + "from semantic_kernel.plugin_definition import kernel_function\n", "\n", "\n", "class GenerateNamesPlugin:\n", @@ -449,9 +449,9 @@ " \"\"\"\n", "\n", " # The default function name will be the name of the function itself, however you can override this\n", - " # by setting the name= in the @sk_function decorator. In this case, we're using\n", + " # by setting the name= in the @kernel_function decorator. In this case, we're using\n", " # the same name as the function name for simplicity.\n", - " @sk_function(description=\"Generate character names\", name=\"generate_names\")\n", + " @kernel_function(description=\"Generate character names\", name=\"generate_names\")\n", " def generate_names(self) -> str:\n", " \"\"\"\n", " Generate two names.\n", @@ -563,7 +563,7 @@ "\n", "- We've learned how to create native and semantic functions and register them to the kernel\n", "- We've seen how we can use context variables to pass in more custom variables into our prompt\n", - "- We've seen how we can call native functions within semantic function prompts. \n" + "- We've seen how we can call native functions within semantic function prompts.\n" ] } ], diff --git a/python/notebooks/09-groundedness-checking.ipynb b/python/notebooks/09-groundedness-checking.ipynb index d6fcc4bf1854..c02e0f13064b 100644 --- a/python/notebooks/09-groundedness-checking.ipynb +++ b/python/notebooks/09-groundedness-checking.ipynb @@ -82,7 +82,7 @@ "metadata": {}, "outputs": [], "source": [ - "!python -m pip install semantic-kernel==0.4.5.dev0" + "!python -m pip install semantic-kernel==0.4.6.dev0" ] }, { diff --git a/python/notebooks/10-multiple-results-per-prompt.ipynb b/python/notebooks/10-multiple-results-per-prompt.ipynb index 47b575b02710..dd879254ea53 100644 --- a/python/notebooks/10-multiple-results-per-prompt.ipynb +++ b/python/notebooks/10-multiple-results-per-prompt.ipynb @@ -25,7 +25,7 @@ "metadata": {}, "outputs": [], "source": [ - "!python -m pip install semantic-kernel==0.4.5.dev0" + "!python -m pip install semantic-kernel==0.4.6.dev0" ] }, { diff --git a/python/notebooks/11-streaming-completions.ipynb b/python/notebooks/11-streaming-completions.ipynb index 99a26ddc410a..c9b5aa59b953 100644 --- a/python/notebooks/11-streaming-completions.ipynb +++ b/python/notebooks/11-streaming-completions.ipynb @@ -17,7 +17,7 @@ "metadata": {}, "outputs": [], "source": [ - "!python -m pip install semantic-kernel==0.4.5.dev0" + "!python -m pip install semantic-kernel==0.4.6.dev0" ] }, { diff --git a/python/notebooks/third_party/weaviate-persistent-memory.ipynb b/python/notebooks/third_party/weaviate-persistent-memory.ipynb index 279e5bb46b57..c49ac9f32601 100644 --- a/python/notebooks/third_party/weaviate-persistent-memory.ipynb +++ b/python/notebooks/third_party/weaviate-persistent-memory.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Introduction" + "# Introduction\n" ] }, { @@ -13,21 +13,21 @@ "source": [ "This notebook shows how to replace the `VolatileMemoryStore` memory storage used in a [previous notebook](./06-memory-and-embeddings.ipynb) with a `WeaviateMemoryStore`.\n", "\n", - "`WeaviateMemoryStore` is an example of a persistent (i.e. long-term) memory store backed by the Weaviate vector database." + "`WeaviateMemoryStore` is an example of a persistent (i.e. long-term) memory store backed by the Weaviate vector database.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "# About Weaviate" + "# About Weaviate\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "[Weaviate](https://weaviate.io/) is an open-source vector database designed to scale seamlessly into billions of data objects. This implementation supports hybrid search out-of-the-box (meaning it will perform better for keyword searches)." + "[Weaviate](https://weaviate.io/) is an open-source vector database designed to scale seamlessly into billions of data objects. This implementation supports hybrid search out-of-the-box (meaning it will perform better for keyword searches).\n" ] }, { @@ -79,7 +79,7 @@ " To configure a self-hosted instance with Kubernetes, follow Weaviate's [documentation](https://weaviate.io/developers/weaviate/installation/kubernetes).|\n", "\n", "- **Embedded** - start a weaviate instance right from your application code using the client library\n", - " \n", + "\n", " This code snippet shows how to instantiate an embedded weaviate instance and upload a document:\n", "\n", " ```python\n", @@ -97,15 +97,15 @@ "\n", " client.data_object.create(data_obj, \"Wine\")\n", " ```\n", - " \n", - " Refer to the [documentation](https://weaviate.io/developers/weaviate/installation/embedded) for more details about this deployment method." + "\n", + " Refer to the [documentation](https://weaviate.io/developers/weaviate/installation/embedded) for more details about this deployment method.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "# Setup" + "# Setup\n" ] }, { @@ -124,8 +124,9 @@ "metadata": {}, "source": [ "## OS-specific notes:\n", - "* if you run into SSL errors when connecting to OpenAI on macOS, see this issue for a [potential solution](https://github.com/microsoft/semantic-kernel/issues/627#issuecomment-1580912248)\n", - "* on Windows, you may need to run Docker Desktop as administrator" + "\n", + "- if you run into SSL errors when connecting to OpenAI on macOS, see this issue for a [potential solution](https://github.com/microsoft/semantic-kernel/issues/627#issuecomment-1580912248)\n", + "- on Windows, you may need to run Docker Desktop as administrator\n" ] }, { @@ -150,9 +151,10 @@ "metadata": {}, "source": [ "First, we instantiate the Weaviate memory store. Uncomment ONE of the options below, depending on how you want to use Weaviate:\n", - "* from a Docker instance\n", - "* from WCS\n", - "* directly from the client (embedded Weaviate), which works on Linux only at the moment" + "\n", + "- from a Docker instance\n", + "- from WCS\n", + "- directly from the client (embedded Weaviate), which works on Linux only at the moment\n" ] }, { @@ -190,7 +192,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Then, we register the memory store to the kernel:" + "Then, we register the memory store to the kernel:\n" ] }, { @@ -220,7 +222,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Let's create some initial memories \"About Me\". We can add memories to our weaviate memory store by using `save_information_async`" + "Let's create some initial memories \"About Me\". We can add memories to our weaviate memory store by using `save_information_async`\n" ] }, { @@ -253,7 +255,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Searching is done through `search_async`:" + "Searching is done through `search_async`:\n" ] }, { @@ -281,7 +283,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Let's see the results of the functions:" + "Let's see the results of the functions:\n" ] }, { @@ -301,7 +303,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Here's how to use the weaviate memory store in a chat application:" + "Here's how to use the weaviate memory store in a chat application:\n" ] }, { @@ -312,7 +314,7 @@ "source": [ "async def setup_chat_with_memory(\n", " kernel: sk.Kernel,\n", - ") -> Tuple[sk.SKFunctionBase, sk.SKContext]:\n", + ") -> Tuple[sk.KernelFunctionBase, sk.KernelContext]:\n", " sk_prompt = \"\"\"\n", " ChatBot can have a conversation with you about any topic.\n", " It can give explicit instructions or say 'I don't know' if\n", @@ -353,7 +355,7 @@ "metadata": {}, "outputs": [], "source": [ - "async def chat(kernel: sk.Kernel, chat_func: sk.SKFunctionBase, context: sk.SKContext) -> bool:\n", + "async def chat(kernel: sk.Kernel, chat_func: sk.KernelFunctionBase, context: sk.KernelContext) -> bool:\n", " try:\n", " user_input = input(\"User:> \")\n", " context[\"user_input\"] = user_input\n", @@ -394,14 +396,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Adding documents to your memory" + "# Adding documents to your memory\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Create a dictionary to hold some files. The key is the hyperlink to the file and the value is the file's content:" + "Create a dictionary to hold some files. The key is the hyperlink to the file and the value is the file's content:\n" ] }, { @@ -432,7 +434,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Use `save_reference_async` to save the file:" + "Use `save_reference_async` to save the file:\n" ] }, { @@ -461,7 +463,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Use `search_async` to ask a question:" + "Use `search_async` to ask a question:\n" ] }, { diff --git a/python/pyproject.toml b/python/pyproject.toml index 2a9c9fb4aee3..dae5308bbafe 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "semantic-kernel" -version = "0.4.5.dev" +version = "0.4.6.dev" description = "Semantic Kernel Python SDK" authors = ["Microsoft "] readme = "pip/README.md" diff --git a/python/samples/kernel-syntax-examples/azure_chat_gpt_with_data_api_function_calling.py b/python/samples/kernel-syntax-examples/azure_chat_gpt_with_data_api_function_calling.py index aa7ce069d075..a3f07b4b5ed5 100644 --- a/python/samples/kernel-syntax-examples/azure_chat_gpt_with_data_api_function_calling.py +++ b/python/samples/kernel-syntax-examples/azure_chat_gpt_with_data_api_function_calling.py @@ -79,7 +79,7 @@ functions = get_function_calling_object(kernel, filter) -async def chat(context: sk.SKContext) -> Tuple[bool, sk.SKContext]: +async def chat(context: sk.KernelContext) -> Tuple[bool, sk.KernelContext]: try: user_input = input("User:> ") context.variables["user_input"] = user_input diff --git a/python/samples/kernel-syntax-examples/chat_gpt_api_function_calling.py b/python/samples/kernel-syntax-examples/chat_gpt_api_function_calling.py index aa9d01c96cd1..1070454bbd24 100644 --- a/python/samples/kernel-syntax-examples/chat_gpt_api_function_calling.py +++ b/python/samples/kernel-syntax-examples/chat_gpt_api_function_calling.py @@ -74,7 +74,7 @@ chat_function = kernel.register_semantic_function("ChatBot", "Chat", function_config) -async def chat(context: sk.SKContext) -> Tuple[bool, sk.SKContext]: +async def chat(context: sk.KernelContext) -> Tuple[bool, sk.KernelContext]: try: user_input = input("User:> ") context.variables["user_input"] = user_input diff --git a/python/samples/kernel-syntax-examples/google_palm_chat_with_memory.py b/python/samples/kernel-syntax-examples/google_palm_chat_with_memory.py index 18cf8a92d7c2..a9a465f53a11 100644 --- a/python/samples/kernel-syntax-examples/google_palm_chat_with_memory.py +++ b/python/samples/kernel-syntax-examples/google_palm_chat_with_memory.py @@ -42,7 +42,7 @@ async def search_memory_examples(kernel: sk.Kernel) -> None: async def setup_chat_with_memory( kernel: sk.Kernel, -) -> Tuple[sk.SKFunctionBase, sk.SKContext]: +) -> Tuple[sk.KernelFunctionBase, sk.KernelContext]: """ When using Google PaLM to chat with memories, a chat prompt template is essential; otherwise, the kernel will send text prompts to the Google PaLM @@ -90,7 +90,7 @@ async def setup_chat_with_memory( return chat_func, context -async def chat(kernel: sk.Kernel, chat_func: sk.SKFunctionBase, context: sk.SKContext) -> bool: +async def chat(kernel: sk.Kernel, chat_func: sk.KernelFunctionBase, context: sk.KernelContext) -> bool: try: user_input = input("User:> ") context["user_input"] = user_input diff --git a/python/samples/kernel-syntax-examples/memory.py b/python/samples/kernel-syntax-examples/memory.py index b6e83a9ef31f..cc238f52e65a 100644 --- a/python/samples/kernel-syntax-examples/memory.py +++ b/python/samples/kernel-syntax-examples/memory.py @@ -35,7 +35,7 @@ async def search_memory_examples(kernel: sk.Kernel) -> None: async def setup_chat_with_memory( kernel: sk.Kernel, -) -> Tuple[sk.SKFunctionBase, sk.SKContext]: +) -> Tuple[sk.KernelFunctionBase, sk.KernelContext]: sk_prompt = """ ChatBot can have a conversation with you about any topic. It can give explicit instructions or say 'I don't know' if @@ -70,7 +70,7 @@ async def setup_chat_with_memory( return chat_func, context -async def chat(kernel: sk.Kernel, chat_func: sk.SKFunctionBase, context: sk.SKContext) -> bool: +async def chat(kernel: sk.Kernel, chat_func: sk.KernelFunctionBase, context: sk.KernelContext) -> bool: try: user_input = input("User:> ") context["user_input"] = user_input diff --git a/python/semantic_kernel/__init__.py b/python/semantic_kernel/__init__.py index 5501362982ee..6dc36a0b3bd5 100644 --- a/python/semantic_kernel/__init__.py +++ b/python/semantic_kernel/__init__.py @@ -3,8 +3,8 @@ from semantic_kernel import core_plugins, memory from semantic_kernel.kernel import Kernel from semantic_kernel.orchestration.context_variables import ContextVariables -from semantic_kernel.orchestration.sk_context import SKContext -from semantic_kernel.orchestration.sk_function_base import SKFunctionBase +from semantic_kernel.orchestration.kernel_context import KernelContext +from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase from semantic_kernel.semantic_functions.chat_prompt_template import ChatPromptTemplate from semantic_kernel.semantic_functions.prompt_template import PromptTemplate from semantic_kernel.semantic_functions.prompt_template_config import ( @@ -48,8 +48,8 @@ "ChatPromptTemplate", "SemanticFunctionConfig", "ContextVariables", - "SKFunctionBase", - "SKContext", + "KernelFunctionBase", + "KernelContext", "memory", "core_plugins", "setup_logging", diff --git a/python/semantic_kernel/connectors/ai/ai_request_settings.py b/python/semantic_kernel/connectors/ai/ai_request_settings.py index 37980155cb27..ca261b1507df 100644 --- a/python/semantic_kernel/connectors/ai/ai_request_settings.py +++ b/python/semantic_kernel/connectors/ai/ai_request_settings.py @@ -2,10 +2,10 @@ from pydantic import Field -from semantic_kernel.sk_pydantic import SKBaseModel +from semantic_kernel.kernel_pydantic import KernelBaseModel -class AIRequestSettings(SKBaseModel): +class AIRequestSettings(KernelBaseModel): """Base class for AI request settings. Can be used by itself or as a base class for other request settings. The methods are used to create specific request settings objects based on the keys in the extension_data field, this way you can create a generic AIRequestSettings object in your application, which get's mapped into the keys of the request settings that each services returns by using the service.get_request_settings() method. diff --git a/python/semantic_kernel/connectors/ai/ai_service_client_base.py b/python/semantic_kernel/connectors/ai/ai_service_client_base.py index 130b387019fc..701421477c8d 100644 --- a/python/semantic_kernel/connectors/ai/ai_service_client_base.py +++ b/python/semantic_kernel/connectors/ai/ai_service_client_base.py @@ -11,10 +11,10 @@ from pydantic import StringConstraints from semantic_kernel.connectors.ai.ai_request_settings import AIRequestSettings -from semantic_kernel.sk_pydantic import SKBaseModel +from semantic_kernel.kernel_pydantic import KernelBaseModel -class AIServiceClientBase(SKBaseModel, ABC): +class AIServiceClientBase(KernelBaseModel, ABC): """Base class for all AI Services. Has a ai_model_id, any other fields have to be defined by the subclasses. diff --git a/python/semantic_kernel/connectors/ai/open_ai/exceptions/content_filter_ai_exception.py b/python/semantic_kernel/connectors/ai/open_ai/exceptions/content_filter_ai_exception.py index 38013eaf3d86..4c20c57b6f9e 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/exceptions/content_filter_ai_exception.py +++ b/python/semantic_kernel/connectors/ai/open_ai/exceptions/content_filter_ai_exception.py @@ -75,7 +75,9 @@ def __init__( self._param = inner_exception.param inner_error = inner_exception.body.get("innererror", {}) - self._content_filter_code = ContentFilterCodes(inner_error.get("code", "")) + self._content_filter_code = ContentFilterCodes( + inner_error.get("code", ContentFilterCodes.RESPONSIBLE_AI_POLICY_VIOLATION.value) + ) self._content_filter_result = dict( [ key, diff --git a/python/semantic_kernel/connectors/ai/open_ai/models/chat/function_call.py b/python/semantic_kernel/connectors/ai/open_ai/models/chat/function_call.py index 416881b00cda..c851fae0bc5c 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/models/chat/function_call.py +++ b/python/semantic_kernel/connectors/ai/open_ai/models/chat/function_call.py @@ -2,11 +2,11 @@ import json from typing import Dict, Tuple +from semantic_kernel.kernel_pydantic import KernelBaseModel from semantic_kernel.orchestration.context_variables import ContextVariables -from semantic_kernel.sk_pydantic import SKBaseModel -class FunctionCall(SKBaseModel): +class FunctionCall(KernelBaseModel): """Class to hold a function call response.""" name: str diff --git a/python/semantic_kernel/connectors/ai/open_ai/request_settings/azure_chat_request_settings.py b/python/semantic_kernel/connectors/ai/open_ai/request_settings/azure_chat_request_settings.py index 22b7fc7cbf0c..dc1d169530c1 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/request_settings/azure_chat_request_settings.py +++ b/python/semantic_kernel/connectors/ai/open_ai/request_settings/azure_chat_request_settings.py @@ -7,7 +7,7 @@ from semantic_kernel.connectors.ai.open_ai.request_settings.open_ai_request_settings import ( OpenAIChatRequestSettings, ) -from semantic_kernel.sk_pydantic import SKBaseModel +from semantic_kernel.kernel_pydantic import KernelBaseModel logger = logging.getLogger(__name__) @@ -72,7 +72,7 @@ class AzureDataSources: # @dataclass -class ExtraBody(SKBaseModel): +class ExtraBody(KernelBaseModel): data_sources: Optional[List[AzureDataSources]] = Field(None, alias="dataSources") input_language: Optional[str] = Field(None, serialization_alias="inputLanguage") output_language: Optional[str] = Field(None, serialization_alias="outputLanguage") diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/azure_chat_completion.py b/python/semantic_kernel/connectors/ai/open_ai/services/azure_chat_completion.py index f05a0bccad7d..da5a2fd90dcb 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/azure_chat_completion.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/azure_chat_completion.py @@ -39,7 +39,7 @@ OpenAITextCompletionBase, ) from semantic_kernel.connectors.ai.open_ai.utils import _parse_choices, _parse_message -from semantic_kernel.sk_pydantic import HttpsUrl +from semantic_kernel.kernel_pydantic import HttpsUrl logger: logging.Logger = logging.getLogger(__name__) diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/azure_config_base.py b/python/semantic_kernel/connectors/ai/open_ai/services/azure_config_base.py index 7da6234fb01a..964c30e50fe3 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/azure_config_base.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/azure_config_base.py @@ -17,7 +17,7 @@ OpenAIModelTypes, ) from semantic_kernel.connectors.telemetry import APP_INFO -from semantic_kernel.sk_pydantic import HttpsUrl +from semantic_kernel.kernel_pydantic import HttpsUrl logger: logging.Logger = logging.getLogger(__name__) diff --git a/python/semantic_kernel/connectors/ai/open_ai/utils.py b/python/semantic_kernel/connectors/ai/open_ai/utils.py index 4354162af192..dc20faa49d6c 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/utils.py +++ b/python/semantic_kernel/connectors/ai/open_ai/utils.py @@ -6,17 +6,17 @@ from openai.types.chat import ChatCompletion -from semantic_kernel import Kernel, SKContext +from semantic_kernel import Kernel, KernelContext from semantic_kernel.connectors.ai.open_ai.models.chat.function_call import FunctionCall from semantic_kernel.connectors.ai.open_ai.semantic_functions.open_ai_chat_prompt_template import ( OpenAIChatPromptTemplate, ) -from semantic_kernel.orchestration.sk_function_base import SKFunctionBase +from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase logger: logging.Logger = logging.getLogger(__name__) -def _describe_function(function: SKFunctionBase) -> Dict[str, str]: +def _describe_function(function: KernelFunctionBase) -> Dict[str, str]: """Create the object used for function_calling. Assumes that arguments for semantic functions are optional, for native functions required. @@ -105,14 +105,14 @@ async def execute_function_call(kernel: Kernel, function_call: FunctionCall, log async def chat_completion_with_function_call( kernel: Kernel, - context: SKContext, + context: KernelContext, chat_plugin_name: Optional[str] = None, chat_function_name: Optional[str] = None, - chat_function: Optional[SKFunctionBase] = None, + chat_function: Optional[KernelFunctionBase] = None, *, log: Optional[Any] = None, **kwargs: Dict[str, Any], -) -> SKContext: +) -> KernelContext: """Perform a chat completion with auto-executing function calling. This is a recursive function that will execute the chat function multiple times, diff --git a/python/semantic_kernel/connectors/openapi/__init__.py b/python/semantic_kernel/connectors/openapi/__init__.py index 509b0ab4b116..d0880d318f1e 100644 --- a/python/semantic_kernel/connectors/openapi/__init__.py +++ b/python/semantic_kernel/connectors/openapi/__init__.py @@ -1,4 +1,4 @@ -from semantic_kernel.connectors.openapi.sk_openapi import register_openapi_plugin +from semantic_kernel.connectors.openapi.kernel_openapi import register_openapi_plugin __all__ = [ "register_openapi_plugin", diff --git a/python/semantic_kernel/connectors/openapi/sk_openapi.py b/python/semantic_kernel/connectors/openapi/kernel_openapi.py similarity index 89% rename from python/semantic_kernel/connectors/openapi/sk_openapi.py rename to python/semantic_kernel/connectors/openapi/kernel_openapi.py index bd761fb166ff..6e309e248c2c 100644 --- a/python/semantic_kernel/connectors/openapi/sk_openapi.py +++ b/python/semantic_kernel/connectors/openapi/kernel_openapi.py @@ -10,13 +10,13 @@ from openapi_core.exceptions import OpenAPIError from prance import ResolvingParser -from semantic_kernel import Kernel, SKContext +from semantic_kernel import Kernel, KernelContext from semantic_kernel.connectors.ai.open_ai.const import ( USER_AGENT, ) from semantic_kernel.connectors.telemetry import HTTP_USER_AGENT -from semantic_kernel.orchestration.sk_function_base import SKFunctionBase -from semantic_kernel.plugin_definition import sk_function, sk_function_context_parameter +from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase +from semantic_kernel.plugin_definition import kernel_function, kernel_function_context_parameter logger: logging.Logger = logging.getLogger(__name__) @@ -243,7 +243,7 @@ async def run_operation( :param kernel: The kernel to register the plugin with :param plugin_name: The name of the plugin :param openapi_document: The OpenAPI document to register. Can be a filename or URL -:return: A dictionary of SKFunctions keyed by operationId +:return: A dictionary of KernelFunctions keyed by operationId """ @@ -251,7 +251,7 @@ def register_openapi_plugin( kernel: Kernel, plugin_name: str, openapi_document: str, -) -> Dict[str, SKFunctionBase]: +) -> Dict[str, KernelFunctionBase]: parser = OpenApiParser() parsed_doc = parser.parse(openapi_document) operations = parser.create_rest_api_operations(parsed_doc) @@ -260,19 +260,19 @@ def register_openapi_plugin( plugin = {} def create_run_operation_function(runner: OpenApiRunner, operation: RestApiOperation): - @sk_function( + @kernel_function( description=operation.summary if operation.summary else operation.description, name=operation_id, ) - @sk_function_context_parameter(name="path_params", description="A dictionary of path parameters") - @sk_function_context_parameter(name="query_params", description="A dictionary of query parameters") - @sk_function_context_parameter(name="headers", description="A dictionary of headers") - @sk_function_context_parameter(name="request_body", description="A dictionary of the request body") - async def run_openapi_operation(sk_context: SKContext) -> str: - path_params = sk_context.variables.get("path_params") - query_params = sk_context.variables.get("query_params") - headers = sk_context.variables.get("headers") - request_body = sk_context.variables.get("request_body") + @kernel_function_context_parameter(name="path_params", description="A dictionary of path parameters") + @kernel_function_context_parameter(name="query_params", description="A dictionary of query parameters") + @kernel_function_context_parameter(name="headers", description="A dictionary of headers") + @kernel_function_context_parameter(name="request_body", description="A dictionary of the request body") + async def run_openapi_operation(kernel_context: KernelContext) -> str: + path_params = kernel_context.variables.get("path_params") + query_params = kernel_context.variables.get("query_params") + headers = kernel_context.variables.get("headers") + request_body = kernel_context.variables.get("request_body") response = await runner.run_operation( operation, diff --git a/python/semantic_kernel/core_plugins/conversation_summary_plugin.py b/python/semantic_kernel/core_plugins/conversation_summary_plugin.py index ca1cc47d748d..c20bdbdf5671 100644 --- a/python/semantic_kernel/core_plugins/conversation_summary_plugin.py +++ b/python/semantic_kernel/core_plugins/conversation_summary_plugin.py @@ -3,7 +3,7 @@ if TYPE_CHECKING: from semantic_kernel.kernel import Kernel - from semantic_kernel.orchestration.sk_context import SKContext + from semantic_kernel.orchestration.kernel_context import KernelContext class ConversationSummaryPlugin: @@ -11,7 +11,7 @@ class ConversationSummaryPlugin: Semantic plugin that enables conversations summarization. """ - from semantic_kernel.plugin_definition import sk_function + from semantic_kernel.plugin_definition import kernel_function # The max tokens to process in a single semantic function call. _max_tokens = 1024 @@ -36,18 +36,18 @@ def __init__(self, kernel: "Kernel"): top_p=0.5, ) - @sk_function( + @kernel_function( description="Given a long conversation transcript, summarize the conversation.", name="SummarizeConversation", input_description="A long conversation transcript.", ) - async def summarize_conversation_async(self, input: str, context: "SKContext") -> "SKContext": + async def summarize_conversation_async(self, input: str, context: "KernelContext") -> "KernelContext": """ Given a long conversation transcript, summarize the conversation. :param input: A long conversation transcript. - :param context: The SKContext for function execution. - :return: SKContext with the summarized conversation result. + :param context: The KernelContext for function execution. + :return: KernelContext with the summarized conversation result. """ from semantic_kernel.text import text_chunker from semantic_kernel.text.function_extension import ( diff --git a/python/semantic_kernel/core_plugins/file_io_plugin.py b/python/semantic_kernel/core_plugins/file_io_plugin.py index b5dd173321e6..c67e68e0bf54 100644 --- a/python/semantic_kernel/core_plugins/file_io_plugin.py +++ b/python/semantic_kernel/core_plugins/file_io_plugin.py @@ -5,14 +5,14 @@ import aiofiles -from semantic_kernel.plugin_definition import sk_function, sk_function_context_parameter -from semantic_kernel.sk_pydantic import SKBaseModel +from semantic_kernel.kernel_pydantic import KernelBaseModel +from semantic_kernel.plugin_definition import kernel_function, kernel_function_context_parameter if t.TYPE_CHECKING: - from semantic_kernel.orchestration.sk_context import SKContext + from semantic_kernel.orchestration.kernel_context import KernelContext -class FileIOPlugin(SKBaseModel): +class FileIOPlugin(KernelBaseModel): """ Description: Read and write from a file. @@ -25,7 +25,7 @@ class FileIOPlugin(SKBaseModel): {{file.writeAsync}} """ - @sk_function( + @kernel_function( description="Read a file", name="readAsync", input_description="Path of the source file", @@ -49,13 +49,13 @@ async def read_async(self, path: str) -> str: content = await fp.read() return content - @sk_function( + @kernel_function( description="Write a file", name="writeAsync", ) - @sk_function_context_parameter(name="path", description="Destination path") - @sk_function_context_parameter(name="content", description="File content") - async def write_async(self, context: "SKContext") -> None: + @kernel_function_context_parameter(name="path", description="Destination path") + @kernel_function_context_parameter(name="content", description="File content") + async def write_async(self, context: "KernelContext") -> None: """ Write a file diff --git a/python/semantic_kernel/core_plugins/http_plugin.py b/python/semantic_kernel/core_plugins/http_plugin.py index 1e3e4cf9200e..2b707a51fe6e 100644 --- a/python/semantic_kernel/core_plugins/http_plugin.py +++ b/python/semantic_kernel/core_plugins/http_plugin.py @@ -5,14 +5,14 @@ import aiohttp -from semantic_kernel.plugin_definition import sk_function, sk_function_context_parameter -from semantic_kernel.sk_pydantic import SKBaseModel +from semantic_kernel.kernel_pydantic import KernelBaseModel +from semantic_kernel.plugin_definition import kernel_function, kernel_function_context_parameter if t.TYPE_CHECKING: - from semantic_kernel.orchestration.sk_context import SKContext + from semantic_kernel.orchestration.kernel_context import KernelContext -class HttpPlugin(SKBaseModel): +class HttpPlugin(KernelBaseModel): """ A plugin that provides HTTP functionality. @@ -27,7 +27,7 @@ class HttpPlugin(SKBaseModel): {{http.deleteAsync $url}} """ - @sk_function(description="Makes a GET request to a uri", name="getAsync") + @kernel_function(description="Makes a GET request to a uri", name="getAsync") async def get_async(self, url: str) -> str: """ Sends an HTTP GET request to the specified URI and returns @@ -44,9 +44,9 @@ async def get_async(self, url: str) -> str: async with session.get(url, raise_for_status=True) as response: return await response.text() - @sk_function(description="Makes a POST request to a uri", name="postAsync") - @sk_function_context_parameter(name="body", description="The body of the request") - async def post_async(self, url: str, context: "SKContext") -> str: + @kernel_function(description="Makes a POST request to a uri", name="postAsync") + @kernel_function_context_parameter(name="body", description="The body of the request") + async def post_async(self, url: str, context: "KernelContext") -> str: """ Sends an HTTP POST request to the specified URI and returns the response body as a string. @@ -67,9 +67,9 @@ async def post_async(self, url: str, context: "SKContext") -> str: async with session.post(url, headers=headers, data=data, raise_for_status=True) as response: return await response.text() - @sk_function(description="Makes a PUT request to a uri", name="putAsync") - @sk_function_context_parameter(name="body", description="The body of the request") - async def put_async(self, url: str, context: "SKContext") -> str: + @kernel_function(description="Makes a PUT request to a uri", name="putAsync") + @kernel_function_context_parameter(name="body", description="The body of the request") + async def put_async(self, url: str, context: "KernelContext") -> str: """ Sends an HTTP PUT request to the specified URI and returns the response body as a string. @@ -89,7 +89,7 @@ async def put_async(self, url: str, context: "SKContext") -> str: async with session.put(url, headers=headers, data=data, raise_for_status=True) as response: return await response.text() - @sk_function(description="Makes a DELETE request to a uri", name="deleteAsync") + @kernel_function(description="Makes a DELETE request to a uri", name="deleteAsync") async def delete_async(self, url: str) -> str: """ Sends an HTTP DELETE request to the specified URI and returns diff --git a/python/semantic_kernel/core_plugins/math_plugin.py b/python/semantic_kernel/core_plugins/math_plugin.py index 414db7718b44..9d42f2be1c81 100644 --- a/python/semantic_kernel/core_plugins/math_plugin.py +++ b/python/semantic_kernel/core_plugins/math_plugin.py @@ -1,14 +1,14 @@ # Copyright (c) Microsoft. All rights reserved. import typing as t -from semantic_kernel.plugin_definition import sk_function, sk_function_context_parameter -from semantic_kernel.sk_pydantic import SKBaseModel +from semantic_kernel.kernel_pydantic import KernelBaseModel +from semantic_kernel.plugin_definition import kernel_function, kernel_function_context_parameter if t.TYPE_CHECKING: - from semantic_kernel.orchestration.sk_context import SKContext + from semantic_kernel.orchestration.kernel_context import KernelContext -class MathPlugin(SKBaseModel): +class MathPlugin(KernelBaseModel): """ Description: MathPlugin provides a set of functions to make Math calculations. @@ -16,21 +16,21 @@ class MathPlugin(SKBaseModel): kernel.import_plugin(MathPlugin(), plugin_name="math") Examples: - {{math.Add}} => Returns the sum of initial_value_text and Amount (provided in the SKContext) + {{math.Add}} => Returns the sum of initial_value_text and Amount (provided in the KernelContext) """ - @sk_function( + @kernel_function( description="Adds value to a value", name="Add", input_description="The value to add", ) - @sk_function_context_parameter( + @kernel_function_context_parameter( name="Amount", description="Amount to add", type="number", required=True, ) - def add(self, initial_value_text: str, context: "SKContext") -> str: + def add(self, initial_value_text: str, context: "KernelContext") -> str: """ Returns the Addition result of initial and amount values provided. @@ -40,18 +40,18 @@ def add(self, initial_value_text: str, context: "SKContext") -> str: """ return MathPlugin.add_or_subtract(initial_value_text, context, add=True) - @sk_function( + @kernel_function( description="Subtracts value to a value", name="Subtract", input_description="The value to subtract", ) - @sk_function_context_parameter( + @kernel_function_context_parameter( name="Amount", description="Amount to subtract", type="number", required=True, ) - def subtract(self, initial_value_text: str, context: "SKContext") -> str: + def subtract(self, initial_value_text: str, context: "KernelContext") -> str: """ Returns the difference of numbers provided. @@ -62,7 +62,7 @@ def subtract(self, initial_value_text: str, context: "SKContext") -> str: return MathPlugin.add_or_subtract(initial_value_text, context, add=False) @staticmethod - def add_or_subtract(initial_value_text: str, context: "SKContext", add: bool) -> str: + def add_or_subtract(initial_value_text: str, context: "KernelContext", add: bool) -> str: """ Helper function to perform addition or subtraction based on the add flag. diff --git a/python/semantic_kernel/core_plugins/text_memory_plugin.py b/python/semantic_kernel/core_plugins/text_memory_plugin.py index c5bcdb0af3d5..8a5a3de1a50c 100644 --- a/python/semantic_kernel/core_plugins/text_memory_plugin.py +++ b/python/semantic_kernel/core_plugins/text_memory_plugin.py @@ -4,16 +4,16 @@ import typing as t from typing import ClassVar -from semantic_kernel.plugin_definition import sk_function, sk_function_context_parameter -from semantic_kernel.sk_pydantic import SKBaseModel +from semantic_kernel.kernel_pydantic import KernelBaseModel +from semantic_kernel.plugin_definition import kernel_function, kernel_function_context_parameter if t.TYPE_CHECKING: - from semantic_kernel.orchestration.sk_context import SKContext + from semantic_kernel.orchestration.kernel_context import KernelContext logger: logging.Logger = logging.getLogger(__name__) -class TextMemoryPlugin(SKBaseModel): +class TextMemoryPlugin(KernelBaseModel): COLLECTION_PARAM: ClassVar[str] = "collection" RELEVANCE_PARAM: ClassVar[str] = "relevance" KEY_PARAM: ClassVar[str] = "key" @@ -23,32 +23,32 @@ class TextMemoryPlugin(SKBaseModel): DEFAULT_LIMIT: ClassVar[int] = "1" # @staticmethod - @sk_function( + @kernel_function( description="Recall a fact from the long term memory", name="recall", input_description="The information to retrieve", ) - @sk_function_context_parameter( + @kernel_function_context_parameter( name=COLLECTION_PARAM, description="The collection to search for information", default_value=DEFAULT_COLLECTION, ) - @sk_function_context_parameter( + @kernel_function_context_parameter( name=RELEVANCE_PARAM, description="The relevance score, from 0.0 to 1.0; 1.0 means perfect match", default_value=DEFAULT_RELEVANCE, ) - @sk_function_context_parameter( + @kernel_function_context_parameter( name=LIMIT_PARAM, description="The maximum number of relevant memories to recall.", default_value=DEFAULT_LIMIT, ) - async def recall_async(self, ask: str, context: "SKContext") -> str: + async def recall_async(self, ask: str, context: "KernelContext") -> str: """ Recall a fact from the long term memory. Example: - sk_context["input"] = "what is the capital of France?" + context["input"] = "what is the capital of France?" {{memory.recall $input}} => "Paris" Args: @@ -90,27 +90,27 @@ async def recall_async(self, ask: str, context: "SKContext") -> str: return results[0].text if limit == 1 else json.dumps([r.text for r in results]) - @sk_function( + @kernel_function( description="Save information to semantic memory", name="save", input_description="The information to save", ) - @sk_function_context_parameter( + @kernel_function_context_parameter( name=COLLECTION_PARAM, description="The collection to save the information", default_value=DEFAULT_COLLECTION, ) - @sk_function_context_parameter( + @kernel_function_context_parameter( name=KEY_PARAM, description="The unique key to associate with the information", ) - async def save_async(self, text: str, context: "SKContext") -> None: + async def save_async(self, text: str, context: "KernelContext") -> None: """ Save a fact to the long term memory. Example: - sk_context["input"] = "the capital of France is Paris" - sk_context[TextMemoryPlugin.KEY_PARAM] = "countryInfo1" + context["input"] = "the capital of France is Paris" + context[TextMemoryPlugin.KEY_PARAM] = "countryInfo1" {{memory.save $input}} Args: diff --git a/python/semantic_kernel/core_plugins/text_plugin.py b/python/semantic_kernel/core_plugins/text_plugin.py index 69b5e4fef61e..8ff12b370a6c 100644 --- a/python/semantic_kernel/core_plugins/text_plugin.py +++ b/python/semantic_kernel/core_plugins/text_plugin.py @@ -1,10 +1,10 @@ # Copyright (c) Microsoft. All rights reserved. -from semantic_kernel.plugin_definition import sk_function -from semantic_kernel.sk_pydantic import SKBaseModel +from semantic_kernel.kernel_pydantic import KernelBaseModel +from semantic_kernel.plugin_definition import kernel_function -class TextPlugin(SKBaseModel): +class TextPlugin(KernelBaseModel): """ TextPlugin provides a set of functions to manipulate strings. @@ -12,73 +12,73 @@ class TextPlugin(SKBaseModel): kernel.import_plugin(TextPlugin(), plugin_name="text") Examples: - SKContext["input"] = " hello world " + KernelContext["input"] = " hello world " {{text.trim $input}} => "hello world" - SKContext["input"] = " hello world " + KernelContext["input"] = " hello world " {{text.trimStart $input} => "hello world " - SKContext["input"] = " hello world " + KernelContext["input"] = " hello world " {{text.trimEnd $input} => " hello world" - SKContext["input"] = "hello world" + KernelContext["input"] = "hello world" {{text.uppercase $input}} => "HELLO WORLD" - SKContext["input"] = "HELLO WORLD" + KernelContext["input"] = "HELLO WORLD" {{text.lowercase $input}} => "hello world" """ - @sk_function(description="Trim whitespace from the start and end of a string.") + @kernel_function(description="Trim whitespace from the start and end of a string.") def trim(self, text: str) -> str: """ Trim whitespace from the start and end of a string. Example: - SKContext["input"] = " hello world " + KernelContext["input"] = " hello world " {{text.trim $input}} => "hello world" """ return text.strip() - @sk_function(description="Trim whitespace from the start of a string.") + @kernel_function(description="Trim whitespace from the start of a string.") def trim_start(self, text: str) -> str: """ Trim whitespace from the start of a string. Example: - SKContext["input"] = " hello world " + KernelContext["input"] = " hello world " {{text.trim $input}} => "hello world " """ return text.lstrip() - @sk_function(description="Trim whitespace from the end of a string.") + @kernel_function(description="Trim whitespace from the end of a string.") def trim_end(self, text: str) -> str: """ Trim whitespace from the end of a string. Example: - SKContext["input"] = " hello world " + KernelContext["input"] = " hello world " {{text.trim $input}} => " hello world" """ return text.rstrip() - @sk_function(description="Convert a string to uppercase.") + @kernel_function(description="Convert a string to uppercase.") def uppercase(self, text: str) -> str: """ Convert a string to uppercase. Example: - SKContext["input"] = "hello world" + KernelContext["input"] = "hello world" {{text.uppercase $input}} => "HELLO WORLD" """ return text.upper() - @sk_function(description="Convert a string to lowercase.") + @kernel_function(description="Convert a string to lowercase.") def lowercase(self, text: str) -> str: """ Convert a string to lowercase. Example: - SKContext["input"] = "HELLO WORLD" + KernelContext["input"] = "HELLO WORLD" {{text.lowercase $input}} => "hello world" """ return text.lower() diff --git a/python/semantic_kernel/core_plugins/time_plugin.py b/python/semantic_kernel/core_plugins/time_plugin.py index 00f132de04c8..7f5fa81d6c3c 100644 --- a/python/semantic_kernel/core_plugins/time_plugin.py +++ b/python/semantic_kernel/core_plugins/time_plugin.py @@ -2,11 +2,11 @@ import datetime -from semantic_kernel.plugin_definition import sk_function -from semantic_kernel.sk_pydantic import SKBaseModel +from semantic_kernel.kernel_pydantic import KernelBaseModel +from semantic_kernel.plugin_definition import kernel_function -class TimePlugin(SKBaseModel): +class TimePlugin(KernelBaseModel): """ Description: TimePlugin provides a set of functions to get the current time and date. @@ -38,7 +38,7 @@ class TimePlugin(SKBaseModel): {{time.timeZoneName}} => PST """ - @sk_function(description="Get the current date.") + @kernel_function(description="Get the current date.") def date(self) -> str: """ Get the current date @@ -49,7 +49,7 @@ def date(self) -> str: now = datetime.datetime.now() return now.strftime("%A, %d %B, %Y") - @sk_function(description="Get the current date.") + @kernel_function(description="Get the current date.") def today(self) -> str: """ Get the current date @@ -59,7 +59,7 @@ def today(self) -> str: """ return self.date() - @sk_function(description="Get the current date in iso format.") + @kernel_function(description="Get the current date in iso format.") def iso_date(self) -> str: """ Get the current date in iso format @@ -70,7 +70,7 @@ def iso_date(self) -> str: today = datetime.date.today() return today.isoformat() - @sk_function(description="Get the current date and time in the local time zone") + @kernel_function(description="Get the current date and time in the local time zone") def now(self) -> str: """ Get the current date and time in the local time zone" @@ -81,7 +81,7 @@ def now(self) -> str: now = datetime.datetime.now() return now.strftime("%A, %B %d, %Y %I:%M %p") - @sk_function(description="Get the current date and time in UTC", name="utcNow") + @kernel_function(description="Get the current date and time in UTC", name="utcNow") def utc_now(self) -> str: """ Get the current date and time in UTC @@ -92,7 +92,7 @@ def utc_now(self) -> str: now = datetime.datetime.utcnow() return now.strftime("%A, %B %d, %Y %I:%M %p") - @sk_function(description="Get the current time in the local time zone") + @kernel_function(description="Get the current time in the local time zone") def time(self) -> str: """ Get the current time in the local time zone @@ -103,7 +103,7 @@ def time(self) -> str: now = datetime.datetime.now() return now.strftime("%I:%M:%S %p") - @sk_function(description="Get the current year") + @kernel_function(description="Get the current year") def year(self) -> str: """ Get the current year @@ -114,7 +114,7 @@ def year(self) -> str: now = datetime.datetime.now() return now.strftime("%Y") - @sk_function(description="Get the current month") + @kernel_function(description="Get the current month") def month(self) -> str: """ Get the current month @@ -125,7 +125,7 @@ def month(self) -> str: now = datetime.datetime.now() return now.strftime("%B") - @sk_function(description="Get the current month number") + @kernel_function(description="Get the current month number") def month_number(self) -> str: """ Get the current month number @@ -136,7 +136,7 @@ def month_number(self) -> str: now = datetime.datetime.now() return now.strftime("%m") - @sk_function(description="Get the current day") + @kernel_function(description="Get the current day") def day(self) -> str: """ Get the current day of the month @@ -147,7 +147,7 @@ def day(self) -> str: now = datetime.datetime.now() return now.strftime("%d") - @sk_function(description="Get the current day of the week", name="dayOfWeek") + @kernel_function(description="Get the current day of the week", name="dayOfWeek") def day_of_week(self) -> str: """ Get the current day of the week @@ -158,7 +158,7 @@ def day_of_week(self) -> str: now = datetime.datetime.now() return now.strftime("%A") - @sk_function(description="Get the current hour") + @kernel_function(description="Get the current hour") def hour(self) -> str: """ Get the current hour @@ -169,7 +169,7 @@ def hour(self) -> str: now = datetime.datetime.now() return now.strftime("%I %p") - @sk_function(description="Get the current hour number", name="hourNumber") + @kernel_function(description="Get the current hour number", name="hourNumber") def hour_number(self) -> str: """ Get the current hour number @@ -180,7 +180,7 @@ def hour_number(self) -> str: now = datetime.datetime.now() return now.strftime("%H") - @sk_function(description="Get the current minute") + @kernel_function(description="Get the current minute") def minute(self) -> str: """ Get the current minute @@ -191,7 +191,7 @@ def minute(self) -> str: now = datetime.datetime.now() return now.strftime("%M") - @sk_function(description="Get the date of offset from today by a provided number of days") + @kernel_function(description="Get the date of offset from today by a provided number of days") def days_ago(self, days: str) -> str: """ Get the date a provided number of days in the past @@ -202,13 +202,13 @@ def days_ago(self, days: str) -> str: The date of the offset day. Example: - SKContext["input"] = "3" + KernelContext["input"] = "3" {{time.days_ago $input}} => Sunday, 7 May, 2023 """ d = datetime.date.today() - datetime.timedelta(days=int(days)) return d.strftime("%A, %d %B, %Y") - @sk_function( + @kernel_function( description="""Get the date of the last day matching the supplied week day name in English. Example: Che giorno era 'Martedi' scorso -> dateMatchingLastDayName 'Tuesday' => Tuesday, 16 May, 2023""" @@ -223,7 +223,7 @@ def date_matching_last_day_name(self, day_name: str) -> str: The date of the matching day. Example: - SKContext["input"] = "Sunday" + KernelContext["input"] = "Sunday" {{time.date_matching_last_day_name $input}} => Sunday, 7 May, 2023 """ d = datetime.date.today() @@ -233,7 +233,7 @@ def date_matching_last_day_name(self, day_name: str) -> str: return d.strftime("%A, %d %B, %Y") raise ValueError("day_name is not recognized") - @sk_function(description="Get the seconds on the current minute") + @kernel_function(description="Get the seconds on the current minute") def second(self) -> str: """ Get the seconds on the current minute @@ -244,7 +244,7 @@ def second(self) -> str: now = datetime.datetime.now() return now.strftime("%S") - @sk_function(description="Get the current time zone offset", name="timeZoneOffset") + @kernel_function(description="Get the current time zone offset", name="timeZoneOffset") def time_zone_offset(self) -> str: """ Get the current time zone offset @@ -255,7 +255,7 @@ def time_zone_offset(self) -> str: now = datetime.datetime.now() return now.strftime("%z") - @sk_function(description="Get the current time zone name", name="timeZoneName") + @kernel_function(description="Get the current time zone name", name="timeZoneName") def time_zone_name(self) -> str: """ Get the current time zone name diff --git a/python/semantic_kernel/core_plugins/wait_plugin.py b/python/semantic_kernel/core_plugins/wait_plugin.py index bdf6bb3470b3..ccc77c6baaeb 100644 --- a/python/semantic_kernel/core_plugins/wait_plugin.py +++ b/python/semantic_kernel/core_plugins/wait_plugin.py @@ -2,11 +2,11 @@ import asyncio -from semantic_kernel.plugin_definition import sk_function -from semantic_kernel.sk_pydantic import SKBaseModel +from semantic_kernel.kernel_pydantic import KernelBaseModel +from semantic_kernel.plugin_definition import kernel_function -class WaitPlugin(SKBaseModel): +class WaitPlugin(KernelBaseModel): """ WaitPlugin provides a set of functions to wait for a certain amount of time. @@ -17,7 +17,7 @@ class WaitPlugin(SKBaseModel): {{wait.seconds 5}} => Wait for 5 seconds """ - @sk_function(description="Wait for a certain number of seconds.") + @kernel_function(description="Wait for a certain number of seconds.") async def wait(self, seconds_text: str) -> None: try: seconds = max(float(seconds_text), 0) diff --git a/python/semantic_kernel/core_plugins/web_search_engine_plugin.py b/python/semantic_kernel/core_plugins/web_search_engine_plugin.py index 8d132f6bc454..c8e150430ede 100644 --- a/python/semantic_kernel/core_plugins/web_search_engine_plugin.py +++ b/python/semantic_kernel/core_plugins/web_search_engine_plugin.py @@ -1,10 +1,10 @@ import typing as t from semantic_kernel.connectors.search_engine.connector import ConnectorBase -from semantic_kernel.plugin_definition import sk_function, sk_function_context_parameter +from semantic_kernel.plugin_definition import kernel_function, kernel_function_context_parameter if t.TYPE_CHECKING: - from semantic_kernel.orchestration.sk_context import SKContext + from semantic_kernel.orchestration.kernel_context import KernelContext class WebSearchEnginePlugin: @@ -19,7 +19,7 @@ class WebSearchEnginePlugin: {{WebSearch.SearchAsync "What is semantic kernel?"}} => Returns the first `num_results` number of results for the given search query and ignores the first `offset` number of results - (num_results and offset are specified in SKContext) + (num_results and offset are specified in KernelContext) """ _connector: "ConnectorBase" @@ -27,18 +27,18 @@ class WebSearchEnginePlugin: def __init__(self, connector: "ConnectorBase") -> None: self._connector = connector - @sk_function(description="Performs a web search for a given query", name="searchAsync") - @sk_function_context_parameter( + @kernel_function(description="Performs a web search for a given query", name="searchAsync") + @kernel_function_context_parameter( name="num_results", description="The number of search results to return", default_value="1", ) - @sk_function_context_parameter( + @kernel_function_context_parameter( name="offset", description="The number of search results to skip", default_value="0", ) - async def search_async(self, query: str, context: "SKContext") -> str: + async def search_async(self, query: str, context: "KernelContext") -> str: """ Returns the search results of the query provided. Returns `num_results` results and ignores the first `offset`. diff --git a/python/semantic_kernel/events/function_invoked_event_args.py b/python/semantic_kernel/events/function_invoked_event_args.py index 8e402c4fbe50..68f3e3c1cb3f 100644 --- a/python/semantic_kernel/events/function_invoked_event_args.py +++ b/python/semantic_kernel/events/function_invoked_event_args.py @@ -1,9 +1,9 @@ # Copyright (c) Microsoft. All rights reserved. -from semantic_kernel.events.sk_events_args import SKEventArgs +from semantic_kernel.events.kernel_events_args import KernelEventArgs -class FunctionInvokedEventArgs(SKEventArgs): +class FunctionInvokedEventArgs(KernelEventArgs): def __init__(self, function_view, context): super().__init__(function_view, context) self._repeat_requested = False diff --git a/python/semantic_kernel/events/function_invoking_event_args.py b/python/semantic_kernel/events/function_invoking_event_args.py index a0fdaa261cd2..6f0e42705d3b 100644 --- a/python/semantic_kernel/events/function_invoking_event_args.py +++ b/python/semantic_kernel/events/function_invoking_event_args.py @@ -1,9 +1,9 @@ # Copyright (c) Microsoft. All rights reserved. -from semantic_kernel.events.sk_events_args import SKEventArgs +from semantic_kernel.events.kernel_events_args import KernelEventArgs -class FunctionInvokingEventArgs(SKEventArgs): +class FunctionInvokingEventArgs(KernelEventArgs): def __init__(self, function_view, context): super().__init__(function_view, context) self._skip_requested = False diff --git a/python/semantic_kernel/events/sk_events_args.py b/python/semantic_kernel/events/kernel_events_args.py similarity index 80% rename from python/semantic_kernel/events/sk_events_args.py rename to python/semantic_kernel/events/kernel_events_args.py index bdf0465b45f4..fac02e46c136 100644 --- a/python/semantic_kernel/events/sk_events_args.py +++ b/python/semantic_kernel/events/kernel_events_args.py @@ -1,11 +1,11 @@ # Copyright (c) Microsoft. All rights reserved. -from semantic_kernel.orchestration.sk_context import SKContext +from semantic_kernel.orchestration.kernel_context import KernelContext from semantic_kernel.plugin_definition.function_view import FunctionView -class SKEventArgs: - def __init__(self, function_view: FunctionView, context: SKContext): +class KernelEventArgs: + def __init__(self, function_view: FunctionView, context: KernelContext): if context is None or function_view is None: raise ValueError("function_view and context cannot be None") diff --git a/python/semantic_kernel/kernel.py b/python/semantic_kernel/kernel.py index 65df93e6c484..bb0acf7942a6 100644 --- a/python/semantic_kernel/kernel.py +++ b/python/semantic_kernel/kernel.py @@ -26,9 +26,9 @@ from semantic_kernel.memory.semantic_text_memory import SemanticTextMemory from semantic_kernel.memory.semantic_text_memory_base import SemanticTextMemoryBase from semantic_kernel.orchestration.context_variables import ContextVariables -from semantic_kernel.orchestration.sk_context import SKContext -from semantic_kernel.orchestration.sk_function import SKFunction -from semantic_kernel.orchestration.sk_function_base import SKFunctionBase +from semantic_kernel.orchestration.kernel_context import KernelContext +from semantic_kernel.orchestration.kernel_function import KernelFunction +from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase from semantic_kernel.plugin_definition.function_view import FunctionView from semantic_kernel.plugin_definition.plugin_collection import PluginCollection from semantic_kernel.plugin_definition.plugin_collection_base import ( @@ -110,7 +110,7 @@ def register_semantic_function( plugin_name: Optional[str], function_name: str, function_config: SemanticFunctionConfig, - ) -> SKFunctionBase: + ) -> KernelFunctionBase: if plugin_name is None or plugin_name == "": plugin_name = PluginCollection.GLOBAL_PLUGIN assert plugin_name is not None # for type checker @@ -126,14 +126,14 @@ def register_semantic_function( def register_native_function( self, plugin_name: Optional[str], - sk_function: Callable, - ) -> SKFunctionBase: - if not hasattr(sk_function, "__sk_function__"): + kernel_function: Callable, + ) -> KernelFunctionBase: + if not hasattr(kernel_function, "__kernel_function__"): raise KernelException( KernelException.ErrorCodes.InvalidFunctionType, - "sk_function argument must be decorated with @sk_function", + "kernel_function argument must be decorated with @kernel_function", ) - function_name = sk_function.__sk_function_name__ + function_name = kernel_function.__kernel_function_name__ if plugin_name is None or plugin_name == "": plugin_name = PluginCollection.GLOBAL_PLUGIN @@ -142,7 +142,7 @@ def register_native_function( validate_plugin_name(plugin_name) validate_function_name(function_name) - function = SKFunction.from_native_method(sk_function, plugin_name) + function = KernelFunction.from_native_method(kernel_function, plugin_name) if self.plugins.has_function(plugin_name, function_name): raise KernelException( @@ -158,7 +158,7 @@ def register_native_function( async def run_stream_async( self, *functions: Any, - input_context: Optional[SKContext] = None, + input_context: Optional[KernelContext] = None, input_vars: Optional[ContextVariables] = None, input_str: Optional[str] = None, ): @@ -196,7 +196,7 @@ async def run_stream_async( variables = variables.merge_or_overwrite(new_vars=input_vars, overwrite=False) else: variables = ContextVariables() - context = SKContext( + context = KernelContext( variables, self._memory, self._plugin_collection.read_only_plugin_collection, @@ -223,11 +223,11 @@ async def run_stream_async( async def run_async( self, *functions: Any, - input_context: Optional[SKContext] = None, + input_context: Optional[KernelContext] = None, input_vars: Optional[ContextVariables] = None, input_str: Optional[str] = None, **kwargs: Dict[str, Any], - ) -> SKContext: + ) -> KernelContext: # if the user passed in a context, prioritize it, but merge with any other inputs if input_context is not None: context = input_context @@ -251,7 +251,7 @@ async def run_async( variables = variables.merge_or_overwrite(new_vars=input_vars, overwrite=False) else: variables = ContextVariables() - context = SKContext( + context = KernelContext( variables, self._memory, self._plugin_collection.read_only_plugin_collection, @@ -260,8 +260,9 @@ async def run_async( pipeline_step = 0 for func in functions: while True: - assert isinstance(func, SKFunctionBase), ( - "All func arguments to Kernel.run*(inputs, func1, func2, ...) " "must be SKFunctionBase instances" + assert isinstance(func, KernelFunctionBase), ( + "All func arguments to Kernel.run*(inputs, func1, func2, ...) " + "must be KernelFunctionBase instances" ) if context.error_occurred: @@ -333,7 +334,7 @@ async def run_async( return context - def func(self, plugin_name: str, function_name: str) -> SKFunctionBase: + def func(self, plugin_name: str, function_name: str) -> KernelFunctionBase: if self.plugins.has_native_function(plugin_name, function_name): return self.plugins.get_native_function(plugin_name, function_name) @@ -368,14 +369,14 @@ def register_memory(self, memory: SemanticTextMemoryBase) -> None: def register_memory_store(self, memory_store: MemoryStoreBase) -> None: self.use_memory(memory_store) - def create_new_context(self, variables: Optional[ContextVariables] = None) -> SKContext: - return SKContext( + def create_new_context(self, variables: Optional[ContextVariables] = None) -> KernelContext: + return KernelContext( ContextVariables() if not variables else variables, self._memory, self.plugins, ) - def on_function_invoking(self, function_view: FunctionView, context: SKContext) -> FunctionInvokingEventArgs: + def on_function_invoking(self, function_view: FunctionView, context: KernelContext) -> FunctionInvokingEventArgs: if self._function_invoking_handlers: args = FunctionInvokingEventArgs(function_view, context) for handler in self._function_invoking_handlers.values(): @@ -383,7 +384,7 @@ def on_function_invoking(self, function_view: FunctionView, context: SKContext) return args return None - def on_function_invoked(self, function_view: FunctionView, context: SKContext) -> FunctionInvokedEventArgs: + def on_function_invoked(self, function_view: FunctionView, context: KernelContext) -> FunctionInvokedEventArgs: if self._function_invoked_handlers: args = FunctionInvokedEventArgs(function_view, context) for handler in self._function_invoked_handlers.values(): @@ -391,7 +392,7 @@ def on_function_invoked(self, function_view: FunctionView, context: SKContext) - return args return None - def import_plugin(self, plugin_instance: Any, plugin_name: str = "") -> Dict[str, SKFunctionBase]: + def import_plugin(self, plugin_instance: Any, plugin_name: str = "") -> Dict[str, KernelFunctionBase]: if plugin_name.strip() == "": plugin_name = PluginCollection.GLOBAL_PLUGIN logger.debug(f"Importing plugin {plugin_name} into the global namespace") @@ -407,10 +408,10 @@ def import_plugin(self, plugin_instance: Any, plugin_name: str = "") -> Dict[str # Read every method from the plugin instance for _, candidate in candidates: # If the method is a semantic function, register it - if not hasattr(candidate, "__sk_function__"): + if not hasattr(candidate, "__kernel_function__"): continue - functions.append(SKFunction.from_native_method(candidate, plugin_name)) + functions.append(KernelFunction.from_native_method(candidate, plugin_name)) logger.debug(f"Methods imported: {len(functions)}") @@ -627,7 +628,7 @@ def _create_semantic_function( plugin_name: str, function_name: str, function_config: SemanticFunctionConfig, - ) -> SKFunctionBase: + ) -> KernelFunctionBase: function_type = function_config.prompt_template_config.type if not function_type == "completion": raise AIException( @@ -635,7 +636,7 @@ def _create_semantic_function( f"Function type not supported: {function_type}", ) - function = SKFunction.from_semantic_config(plugin_name, function_name, function_config) + function = KernelFunction.from_semantic_config(plugin_name, function_name, function_config) function.request_settings.update_from_ai_request_settings( function_config.prompt_template_config.execution_settings ) @@ -698,7 +699,7 @@ def _create_semantic_function( def import_native_plugin_from_directory( self, parent_directory: str, plugin_directory_name: str - ) -> Dict[str, SKFunctionBase]: + ) -> Dict[str, KernelFunctionBase]: MODULE_NAME = "native_function" validate_plugin_name(plugin_directory_name) @@ -727,7 +728,7 @@ def import_native_plugin_from_directory( def import_semantic_plugin_from_directory( self, parent_directory: str, plugin_directory_name: str - ) -> Dict[str, SKFunctionBase]: + ) -> Dict[str, KernelFunctionBase]: CONFIG_FILE = "config.json" PROMPT_FILE = "skprompt.txt" @@ -775,7 +776,7 @@ def create_semantic_function( plugin_name: Optional[str] = None, description: Optional[str] = None, **kwargs: Any, - ) -> "SKFunctionBase": + ) -> "KernelFunctionBase": function_name = function_name if function_name is not None else f"f_{str(uuid4()).replace('-', '_')}" config = PromptTemplateConfig( diff --git a/python/semantic_kernel/sk_pydantic.py b/python/semantic_kernel/kernel_pydantic.py similarity index 83% rename from python/semantic_kernel/sk_pydantic.py rename to python/semantic_kernel/kernel_pydantic.py index 65b33821b899..f718e748f5bf 100644 --- a/python/semantic_kernel/sk_pydantic.py +++ b/python/semantic_kernel/kernel_pydantic.py @@ -11,12 +11,12 @@ HttpsUrl = Annotated[Url, UrlConstraints(max_length=2083, allowed_schemes=["https"])] -class SKBaseModel(BaseModel): +class KernelBaseModel(BaseModel): """Base class for all pydantic models in the SK.""" model_config = ConfigDict(populate_by_name=True, arbitrary_types_allowed=True, validate_assignment=True) # TODO: remove these aliases in SK v1 -PydanticField = SKBaseModel -SKGenericModel = SKBaseModel +PydanticField = KernelBaseModel +KernelGenericModel = KernelBaseModel diff --git a/python/semantic_kernel/memory/semantic_text_memory_base.py b/python/semantic_kernel/memory/semantic_text_memory_base.py index 794d00176ce5..71a576be5997 100644 --- a/python/semantic_kernel/memory/semantic_text_memory_base.py +++ b/python/semantic_kernel/memory/semantic_text_memory_base.py @@ -3,13 +3,13 @@ from abc import abstractmethod from typing import List, Optional, TypeVar +from semantic_kernel.kernel_pydantic import KernelBaseModel from semantic_kernel.memory.memory_query_result import MemoryQueryResult -from semantic_kernel.sk_pydantic import SKBaseModel SemanticTextMemoryT = TypeVar("SemanticTextMemoryT", bound="SemanticTextMemoryBase") -class SemanticTextMemoryBase(SKBaseModel): +class SemanticTextMemoryBase(KernelBaseModel): @abstractmethod async def save_information_async( self, diff --git a/python/semantic_kernel/models/chat/chat_message.py b/python/semantic_kernel/models/chat/chat_message.py index 0dd90551abd7..e45cc1e2e730 100644 --- a/python/semantic_kernel/models/chat/chat_message.py +++ b/python/semantic_kernel/models/chat/chat_message.py @@ -3,14 +3,14 @@ from pydantic import Field +from semantic_kernel.kernel_pydantic import KernelBaseModel from semantic_kernel.semantic_functions.prompt_template import PromptTemplate -from semantic_kernel.sk_pydantic import SKBaseModel if TYPE_CHECKING: - from semantic_kernel.orchestration.sk_context import SKContext + from semantic_kernel.orchestration.kernel_context import KernelContext -class ChatMessage(SKBaseModel): +class ChatMessage(KernelBaseModel): """Class to hold chat messages.""" role: Optional[str] = "assistant" @@ -22,7 +22,7 @@ def content(self) -> Optional[str]: """Return the content of the message.""" return self.fixed_content - async def render_message_async(self, context: "SKContext") -> None: + async def render_message_async(self, context: "KernelContext") -> None: """Render the message. The first time this is called for a given message, it will render the message with the context at that time. diff --git a/python/semantic_kernel/orchestration/context_variables.py b/python/semantic_kernel/orchestration/context_variables.py index 125d9e3477a6..da2690a59cca 100644 --- a/python/semantic_kernel/orchestration/context_variables.py +++ b/python/semantic_kernel/orchestration/context_variables.py @@ -3,10 +3,10 @@ import pydantic as pdt -from semantic_kernel.sk_pydantic import SKBaseModel +from semantic_kernel.kernel_pydantic import KernelBaseModel -class ContextVariables(SKBaseModel): +class ContextVariables(KernelBaseModel): """Class for the context variables, maintains a dict with keys and values for the variables. The keys are all converted to lower case, both in setting and in getting. diff --git a/python/semantic_kernel/orchestration/delegate_handlers.py b/python/semantic_kernel/orchestration/delegate_handlers.py index 46f4dc9ea601..de9910086108 100644 --- a/python/semantic_kernel/orchestration/delegate_handlers.py +++ b/python/semantic_kernel/orchestration/delegate_handlers.py @@ -1,8 +1,8 @@ # Copyright (c) Microsoft. All rights reserved. from semantic_kernel.kernel_exception import KernelException +from semantic_kernel.kernel_pydantic import KernelBaseModel from semantic_kernel.orchestration.delegate_types import DelegateTypes -from semantic_kernel.sk_pydantic import SKBaseModel def _handles(delegate_type): @@ -13,7 +13,7 @@ def decorator(function): return decorator -class DelegateHandlers(SKBaseModel): +class DelegateHandlers(KernelBaseModel): @staticmethod @_handles(DelegateTypes.Void) async def handle_void(function, context): @@ -33,26 +33,26 @@ async def handle_out_task_string(function, context): return context @staticmethod - @_handles(DelegateTypes.InSKContext) - async def handle_in_sk_context(function, context): + @_handles(DelegateTypes.InKernelContext) + async def handle_in_kernel_context(function, context): function(context) return context @staticmethod - @_handles(DelegateTypes.InSKContextOutString) - async def handle_in_sk_context_out_string(function, context): + @_handles(DelegateTypes.InKernelContextOutString) + async def handle_in_kernel_context_out_string(function, context): context.variables.update(function(context)) return context @staticmethod - @_handles(DelegateTypes.InSKContextOutTaskString) - async def handle_in_sk_context_out_task_string(function, context): + @_handles(DelegateTypes.InKernelContextOutTaskString) + async def handle_in_kernel_context_out_task_string(function, context): context.variables.update(await function(context)) return context @staticmethod - @_handles(DelegateTypes.ContextSwitchInSKContextOutTaskSKContext) - async def handle_context_switch_in_sk_context_out_task_sk_context(function, context): + @_handles(DelegateTypes.ContextSwitchInKernelContextOutTaskKernelContext) + async def handle_context_switch_in_kernel_context_out_task_kernel_context(function, context): # Note: Context Switching: allows the function to replace with a # new context, e.g. to branch execution path context = await function(context) diff --git a/python/semantic_kernel/orchestration/delegate_inference.py b/python/semantic_kernel/orchestration/delegate_inference.py index 37fc56257f33..154fbcc05428 100644 --- a/python/semantic_kernel/orchestration/delegate_inference.py +++ b/python/semantic_kernel/orchestration/delegate_inference.py @@ -4,8 +4,8 @@ from typing import NoReturn from semantic_kernel.kernel_exception import KernelException +from semantic_kernel.kernel_pydantic import KernelBaseModel from semantic_kernel.orchestration.delegate_types import DelegateTypes -from semantic_kernel.sk_pydantic import SKBaseModel def _infers(delegate_type): @@ -19,7 +19,7 @@ def decorator(function): def _is_annotation_of_type(annotation, type_to_match) -> bool: return (annotation is type_to_match) or ( # Handle cases where the annotation is provided as a string to avoid circular imports - # for example: `async def read_async(self, context: "SKContext"):` in file_io_plugin.py + # for example: `async def read_async(self, context: "KernelContext"):` in file_io_plugin.py isinstance(annotation, str) and annotation == type_to_match.__name__ ) @@ -34,9 +34,9 @@ def _return_is_str(signature: Signature) -> bool: def _return_is_context(signature: Signature) -> bool: - from semantic_kernel.orchestration.sk_context import SKContext + from semantic_kernel.orchestration.kernel_context import KernelContext - return _is_annotation_of_type(signature.return_annotation, SKContext) + return _is_annotation_of_type(signature.return_annotation, KernelContext) def _return_is_none(signature: Signature) -> bool: @@ -58,12 +58,12 @@ def _has_first_param_with_type(signature: Signature, annotation, only: bool = Tr def _has_two_params_second_is_context(signature: Signature) -> bool: - from semantic_kernel.orchestration.sk_context import SKContext + from semantic_kernel.orchestration.kernel_context import KernelContext if len(signature.parameters) < 2: return False second_param = list(signature.parameters.values())[1] - return _is_annotation_of_type(second_param.annotation, SKContext) + return _is_annotation_of_type(second_param.annotation, KernelContext) def _first_param_is_str(signature: Signature, only: bool = True) -> bool: @@ -71,12 +71,12 @@ def _first_param_is_str(signature: Signature, only: bool = True) -> bool: def _first_param_is_context(signature: Signature) -> bool: - from semantic_kernel.orchestration.sk_context import SKContext + from semantic_kernel.orchestration.kernel_context import KernelContext - return _has_first_param_with_type(signature, SKContext) + return _has_first_param_with_type(signature, KernelContext) -class DelegateInference(SKBaseModel): +class DelegateInference(KernelBaseModel): @staticmethod @_infers(DelegateTypes.Void) def infer_void(signature: Signature, awaitable: bool, is_asyncgenfunc: bool) -> bool: @@ -102,32 +102,32 @@ def infer_out_task_string(signature: Signature, awaitable: bool, is_asyncgenfunc return matches @staticmethod - @_infers(DelegateTypes.InSKContext) - def infer_in_sk_context(signature: Signature, awaitable: bool, is_asyncgenfunc: bool) -> bool: + @_infers(DelegateTypes.InKernelContext) + def infer_in_kernel_context(signature: Signature, awaitable: bool, is_asyncgenfunc: bool) -> bool: matches = _first_param_is_context(signature) matches = matches and _return_is_none(signature) matches = matches and not awaitable and not is_asyncgenfunc return matches @staticmethod - @_infers(DelegateTypes.InSKContextOutString) - def infer_in_sk_context_out_string(signature: Signature, awaitable: bool, is_asyncgenfunc: bool) -> bool: + @_infers(DelegateTypes.InKernelContextOutString) + def infer_in_kernel_context_out_string(signature: Signature, awaitable: bool, is_asyncgenfunc: bool) -> bool: matches = _first_param_is_context(signature) matches = matches and _return_is_str(signature) matches = matches and not awaitable and not is_asyncgenfunc return matches @staticmethod - @_infers(DelegateTypes.InSKContextOutTaskString) - def infer_in_sk_context_out_task_string(signature: Signature, awaitable: bool, is_asyncgenfunc: bool) -> bool: + @_infers(DelegateTypes.InKernelContextOutTaskString) + def infer_in_kernel_context_out_task_string(signature: Signature, awaitable: bool, is_asyncgenfunc: bool) -> bool: matches = _first_param_is_context(signature) matches = matches and _return_is_str(signature) matches = matches and awaitable return matches @staticmethod - @_infers(DelegateTypes.ContextSwitchInSKContextOutTaskSKContext) - def infer_context_switch_in_sk_context_out_task_sk_context( + @_infers(DelegateTypes.ContextSwitchInKernelContextOutTaskKernelContext) + def infer_context_switch_in_kernel_context_out_task_kernel_context( signature: Signature, awaitable: bool, is_asyncgenfunc: bool ) -> bool: matches = _first_param_is_context(signature) diff --git a/python/semantic_kernel/orchestration/delegate_types.py b/python/semantic_kernel/orchestration/delegate_types.py index b850bb5354a8..b0f505483c98 100644 --- a/python/semantic_kernel/orchestration/delegate_types.py +++ b/python/semantic_kernel/orchestration/delegate_types.py @@ -8,10 +8,10 @@ class DelegateTypes(Enum): Void = 1 OutString = 2 OutTaskString = 3 - InSKContext = 4 - InSKContextOutString = 5 - InSKContextOutTaskString = 6 - ContextSwitchInSKContextOutTaskSKContext = 7 + InKernelContext = 4 + InKernelContextOutString = 5 + InKernelContextOutTaskString = 6 + ContextSwitchInKernelContextOutTaskKernelContext = 7 InString = 8 InStringOutString = 9 InStringOutTaskString = 10 diff --git a/python/semantic_kernel/orchestration/sk_context.py b/python/semantic_kernel/orchestration/kernel_context.py similarity index 95% rename from python/semantic_kernel/orchestration/sk_context.py rename to python/semantic_kernel/orchestration/kernel_context.py index 9c664938de78..1f3bf731b7d7 100644 --- a/python/semantic_kernel/orchestration/sk_context.py +++ b/python/semantic_kernel/orchestration/kernel_context.py @@ -6,6 +6,7 @@ from pydantic import Field, PrivateAttr from semantic_kernel.kernel_exception import KernelException +from semantic_kernel.kernel_pydantic import KernelBaseModel from semantic_kernel.memory.semantic_text_memory_base import ( SemanticTextMemoryBase, SemanticTextMemoryT, @@ -17,12 +18,11 @@ from semantic_kernel.plugin_definition.read_only_plugin_collection_base import ( ReadOnlyPluginCollectionBase, ) -from semantic_kernel.sk_pydantic import SKBaseModel logger: logging.Logger = logging.getLogger(__name__) -class SKContext(SKBaseModel, Generic[SemanticTextMemoryT]): +class KernelContext(KernelBaseModel, Generic[SemanticTextMemoryT]): """Semantic Kernel context.""" memory: SemanticTextMemoryT @@ -43,7 +43,7 @@ def __init__( # TODO: cancellation token? ) -> None: """ - Initializes a new instance of the SKContext class. + Initializes a new instance of the KernelContext class. Arguments: variables {ContextVariables} -- The context variables. @@ -175,7 +175,7 @@ def func(self, plugin_name: str, function_name: str): function_name {str} -- The function name. Returns: - SKFunctionBase -- The function. + KernelFunctionBase -- The function. """ if self.plugin_collection is None: raise ValueError("The plugin collection hasn't been set") @@ -213,7 +213,7 @@ def is_function_registered( function_name {str} -- The function name. Returns: - Tuple[bool, SKFunctionBase] -- A tuple with a boolean indicating + Tuple[bool, KernelFunctionBase] -- A tuple with a boolean indicating whether the function is registered and the function itself (or None). """ self.throw_if_plugin_collection_not_set() diff --git a/python/semantic_kernel/orchestration/sk_function.py b/python/semantic_kernel/orchestration/kernel_function.py similarity index 90% rename from python/semantic_kernel/orchestration/sk_function.py rename to python/semantic_kernel/orchestration/kernel_function.py index fa61b1de188a..cdb6025a9355 100644 --- a/python/semantic_kernel/orchestration/sk_function.py +++ b/python/semantic_kernel/orchestration/kernel_function.py @@ -22,7 +22,7 @@ from semantic_kernel.orchestration.delegate_handlers import DelegateHandlers from semantic_kernel.orchestration.delegate_inference import DelegateInference from semantic_kernel.orchestration.delegate_types import DelegateTypes -from semantic_kernel.orchestration.sk_function_base import SKFunctionBase +from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase from semantic_kernel.plugin_definition.function_view import FunctionView from semantic_kernel.plugin_definition.parameter_view import ParameterView from semantic_kernel.plugin_definition.read_only_plugin_collection_base import ( @@ -34,7 +34,7 @@ ) if TYPE_CHECKING: - from semantic_kernel.orchestration.sk_context import SKContext + from semantic_kernel.orchestration.kernel_context import KernelContext if platform.system() == "Windows" and sys.version_info >= (3, 8, 0): asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) @@ -42,7 +42,7 @@ logger: logging.Logger = logging.getLogger(__name__) -class SKFunction(SKFunctionBase): +class KernelFunction(KernelFunctionBase): """ Semantic Kernel function. """ @@ -57,19 +57,19 @@ class SKFunction(SKFunctionBase): _chat_prompt_template: ChatPromptTemplate @staticmethod - def from_native_method(method, plugin_name="", log=None) -> "SKFunction": + def from_native_method(method, plugin_name="", log=None) -> "KernelFunction": if log: logger.warning("The `log` parameter is deprecated. Please use the `logging` module instead.") if method is None: raise ValueError("Method cannot be `None`") - assert method.__sk_function__ is not None, "Method is not a SK function" - assert method.__sk_function_name__ is not None, "Method name is empty" + assert method.__kernel_function__ is not None, "Method is not a Kernel function" + assert method.__kernel_function_name__ is not None, "Method name is empty" parameters = [] - # sk_function_context_parameters are optionals - if hasattr(method, "__sk_function_context_parameters__"): - for param in method.__sk_function_context_parameters__: + # kernel_function_context_parameters are optionals + if hasattr(method, "__kernel_function_context_parameters__"): + for param in method.__kernel_function_context_parameters__: assert "name" in param, "Parameter name is empty" assert "description" in param, "Parameter description is empty" assert "default_value" in param, "Parameter default value is empty" @@ -85,27 +85,27 @@ def from_native_method(method, plugin_name="", log=None) -> "SKFunction": ) if ( - hasattr(method, "__sk_function_input_description__") - and method.__sk_function_input_description__ is not None - and method.__sk_function_input_description__ != "" + hasattr(method, "__kernel_function_input_description__") + and method.__kernel_function_input_description__ is not None + and method.__kernel_function_input_description__ != "" ): input_param = ParameterView( name="input", - description=method.__sk_function_input_description__, - default_value=method.__sk_function_input_default_value__, + description=method.__kernel_function_input_description__, + default_value=method.__kernel_function_input_default_value__, type="string", required=False, ) parameters = [input_param] + parameters - return SKFunction( + return KernelFunction( delegate_type=DelegateInference.infer_delegate_type(method), delegate_function=method, delegate_stream_function=method, parameters=parameters, - description=method.__sk_function_description__, + description=method.__kernel_function_description__, plugin_name=plugin_name, - function_name=method.__sk_function_name__, + function_name=method.__kernel_function_name__, is_semantic=False, ) @@ -115,13 +115,13 @@ def from_semantic_config( function_name: str, function_config: SemanticFunctionConfig, log: Optional[Any] = None, - ) -> "SKFunction": + ) -> "KernelFunction": if log: logger.warning("The `log` parameter is deprecated. Please use the `logging` module instead.") if function_config is None: raise ValueError("Function configuration cannot be `None`") - async def _local_func(client, request_settings, context: "SKContext", **kwargs): + async def _local_func(client, request_settings, context: "KernelContext", **kwargs): if client is None: raise ValueError("AI LLM service cannot be `None`") @@ -207,8 +207,8 @@ async def _local_stream_func(client, request_settings, context): # TODO: "critical exceptions" context.fail(str(e), e) - return SKFunction( - delegate_type=DelegateTypes.ContextSwitchInSKContextOutTaskSKContext, + return KernelFunction( + delegate_type=DelegateTypes.ContextSwitchInKernelContextOutTaskKernelContext, delegate_function=_local_func, delegate_stream_function=_local_stream_func, parameters=function_config.prompt_template.get_parameters(), @@ -276,32 +276,32 @@ def __init__( self._ai_request_settings = AIRequestSettings() self._chat_prompt_template = kwargs.get("chat_prompt_template", None) - def set_default_plugin_collection(self, plugins: ReadOnlyPluginCollectionBase) -> "SKFunction": + def set_default_plugin_collection(self, plugins: ReadOnlyPluginCollectionBase) -> "KernelFunction": self._plugin_collection = plugins return self - def set_ai_service(self, ai_service: Callable[[], TextCompletionClientBase]) -> "SKFunction": + def set_ai_service(self, ai_service: Callable[[], TextCompletionClientBase]) -> "KernelFunction": if ai_service is None: raise ValueError("AI LLM service factory cannot be `None`") self._verify_is_semantic() self._ai_service = ai_service() return self - def set_chat_service(self, chat_service: Callable[[], ChatCompletionClientBase]) -> "SKFunction": + def set_chat_service(self, chat_service: Callable[[], ChatCompletionClientBase]) -> "KernelFunction": if chat_service is None: raise ValueError("Chat LLM service factory cannot be `None`") self._verify_is_semantic() self._ai_service = chat_service() return self - def set_ai_configuration(self, settings: AIRequestSettings) -> "SKFunction": + def set_ai_configuration(self, settings: AIRequestSettings) -> "KernelFunction": if settings is None: raise ValueError("AI LLM request settings cannot be `None`") self._verify_is_semantic() self._ai_request_settings = settings return self - def set_chat_configuration(self, settings: AIRequestSettings) -> "SKFunction": + def set_chat_configuration(self, settings: AIRequestSettings) -> "KernelFunction": if settings is None: raise ValueError("Chat LLM request settings cannot be `None`") self._verify_is_semantic() @@ -321,11 +321,11 @@ def __call__( self, input: Optional[str] = None, variables: ContextVariables = None, - context: Optional["SKContext"] = None, + context: Optional["KernelContext"] = None, memory: Optional[SemanticTextMemoryBase] = None, settings: Optional[AIRequestSettings] = None, log: Optional[Any] = None, - ) -> "SKContext": + ) -> "KernelContext": if log: logger.warning("The `log` parameter is deprecated. Please use the `logging` module instead.") return self.invoke( @@ -340,18 +340,18 @@ def invoke( self, input: Optional[str] = None, variables: ContextVariables = None, - context: Optional["SKContext"] = None, + context: Optional["KernelContext"] = None, memory: Optional[SemanticTextMemoryBase] = None, settings: Optional[AIRequestSettings] = None, log: Optional[Any] = None, - ) -> "SKContext": - from semantic_kernel.orchestration.sk_context import SKContext + ) -> "KernelContext": + from semantic_kernel.orchestration.kernel_context import KernelContext if log: logger.warning("The `log` parameter is deprecated. Please use the `logging` module instead.") if context is None: - context = SKContext( + context = KernelContext( variables=ContextVariables("") if variables is None else variables, plugin_collection=self._plugin_collection, memory=memory if memory is not None else NullMemory.instance, @@ -390,15 +390,15 @@ async def invoke_async( self, input: Optional[str] = None, variables: ContextVariables = None, - context: Optional["SKContext"] = None, + context: Optional["KernelContext"] = None, memory: Optional[SemanticTextMemoryBase] = None, settings: Optional[AIRequestSettings] = None, **kwargs: Dict[str, Any], - ) -> "SKContext": - from semantic_kernel.orchestration.sk_context import SKContext + ) -> "KernelContext": + from semantic_kernel.orchestration.kernel_context import KernelContext if context is None: - context = SKContext( + context = KernelContext( variables=ContextVariables("") if variables is None else variables, plugin_collection=self._plugin_collection, memory=memory if memory is not None else NullMemory.instance, @@ -422,7 +422,7 @@ async def invoke_async( context.fail(str(e), e) return context - async def _invoke_semantic_async(self, context: "SKContext", settings: AIRequestSettings, **kwargs): + async def _invoke_semantic_async(self, context: "KernelContext", settings: AIRequestSettings, **kwargs): self._verify_is_semantic() self._ensure_context_has_plugins(context) new_context = await self._function(self._ai_service, settings or self._ai_request_settings, context) @@ -466,14 +466,14 @@ async def invoke_stream_async( self, input: Optional[str] = None, variables: ContextVariables = None, - context: Optional["SKContext"] = None, + context: Optional["KernelContext"] = None, memory: Optional[SemanticTextMemoryBase] = None, settings: Optional[AIRequestSettings] = None, ): - from semantic_kernel.orchestration.sk_context import SKContext + from semantic_kernel.orchestration.kernel_context import KernelContext if context is None: - context = SKContext( + context = KernelContext( variables=ContextVariables("") if variables is None else variables, plugin_collection=self._plugin_collection, memory=memory if memory is not None else NullMemory.instance, diff --git a/python/semantic_kernel/orchestration/sk_function_base.py b/python/semantic_kernel/orchestration/kernel_function_base.py similarity index 85% rename from python/semantic_kernel/orchestration/sk_function_base.py rename to python/semantic_kernel/orchestration/kernel_function_base.py index 08d14d909276..301166bf674c 100644 --- a/python/semantic_kernel/orchestration/sk_function_base.py +++ b/python/semantic_kernel/orchestration/kernel_function_base.py @@ -7,19 +7,19 @@ from semantic_kernel.connectors.ai.text_completion_client_base import ( TextCompletionClientBase, ) +from semantic_kernel.kernel_pydantic import KernelBaseModel from semantic_kernel.memory.semantic_text_memory_base import SemanticTextMemoryBase from semantic_kernel.orchestration.context_variables import ContextVariables from semantic_kernel.plugin_definition.function_view import FunctionView -from semantic_kernel.sk_pydantic import SKBaseModel if TYPE_CHECKING: - from semantic_kernel.orchestration.sk_context import SKContext + from semantic_kernel.orchestration.kernel_context import KernelContext from semantic_kernel.plugin_definition.read_only_plugin_collection_base import ( ReadOnlyPluginCollectionBase, ) -class SKFunctionBase(SKBaseModel): +class KernelFunctionBase(KernelBaseModel): @property @abstractmethod def name(self) -> str: @@ -97,20 +97,20 @@ def invoke( self, input: Optional[str] = None, variables: ContextVariables = None, - context: Optional["SKContext"] = None, + context: Optional["KernelContext"] = None, memory: Optional[SemanticTextMemoryBase] = None, settings: Optional[AIRequestSettings] = None, - ) -> "SKContext": + ) -> "KernelContext": """ Invokes the function with an explicit string input Keyword Arguments: input {str} -- The explicit string input (default: {None}) variables {ContextVariables} -- The custom input - context {SKContext} -- The context to use + context {KernelContext} -- The context to use memory: {SemanticTextMemoryBase} -- The memory to use settings {AIRequestSettings} -- LLM completion settings Returns: - SKContext -- The updated context, potentially a new one if + KernelContext -- The updated context, potentially a new one if context switching is implemented. """ pass @@ -120,21 +120,21 @@ async def invoke_async( self, input: Optional[str] = None, variables: ContextVariables = None, - context: Optional["SKContext"] = None, + context: Optional["KernelContext"] = None, memory: Optional[SemanticTextMemoryBase] = None, settings: Optional[AIRequestSettings] = None, **kwargs: Dict[str, Any], - ) -> "SKContext": + ) -> "KernelContext": """ Invokes the function with an explicit string input Keyword Arguments: input {str} -- The explicit string input (default: {None}) variables {ContextVariables} -- The custom input - context {SKContext} -- The context to use + context {KernelContext} -- The context to use memory: {SemanticTextMemoryBase} -- The memory to use settings {AIRequestSettings} -- LLM completion settings Returns: - SKContext -- The updated context, potentially a new one if + KernelContext -- The updated context, potentially a new one if context switching is implemented. """ pass @@ -143,7 +143,7 @@ async def invoke_async( def set_default_plugin_collection( self, plugins: "ReadOnlyPluginCollectionBase", - ) -> "SKFunctionBase": + ) -> "KernelFunctionBase": """ Sets the plugin collection to use when the function is invoked without a context or with a context that doesn't have @@ -153,12 +153,12 @@ def set_default_plugin_collection( plugins {ReadOnlyPluginCollectionBase} -- Kernel's plugin collection Returns: - SKFunctionBase -- The function instance + KernelFunctionBase -- The function instance """ pass @abstractmethod - def set_ai_service(self, service_factory: Callable[[], TextCompletionClientBase]) -> "SKFunctionBase": + def set_ai_service(self, service_factory: Callable[[], TextCompletionClientBase]) -> "KernelFunctionBase": """ Sets the AI service used by the semantic function, passing in a factory method. The factory allows us to lazily instantiate the client and to @@ -168,12 +168,12 @@ def set_ai_service(self, service_factory: Callable[[], TextCompletionClientBase] service_factory -- AI service factory Returns: - SKFunctionBase -- The function instance + KernelFunctionBase -- The function instance """ pass @abstractmethod - def set_ai_configuration(self, settings: AIRequestSettings) -> "SKFunctionBase": + def set_ai_configuration(self, settings: AIRequestSettings) -> "KernelFunctionBase": """ Sets the AI completion settings used with LLM requests @@ -181,6 +181,6 @@ def set_ai_configuration(self, settings: AIRequestSettings) -> "SKFunctionBase": settings {AIRequestSettings} -- LLM completion settings Returns: - SKFunctionBase -- The function instance + KernelFunctionBase -- The function instance """ pass diff --git a/python/semantic_kernel/planning/action_planner/action_planner.py b/python/semantic_kernel/planning/action_planner/action_planner.py index 3f353bd8bd0d..e639a10d7f80 100644 --- a/python/semantic_kernel/planning/action_planner/action_planner.py +++ b/python/semantic_kernel/planning/action_planner/action_planner.py @@ -10,14 +10,14 @@ import regex from semantic_kernel import Kernel -from semantic_kernel.orchestration.sk_context import SKContext -from semantic_kernel.orchestration.sk_function_base import SKFunctionBase +from semantic_kernel.orchestration.kernel_context import KernelContext +from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase from semantic_kernel.planning.action_planner.action_planner_config import ( ActionPlannerConfig, ) from semantic_kernel.planning.plan import Plan from semantic_kernel.planning.planning_exception import PlanningException -from semantic_kernel.plugin_definition import sk_function, sk_function_context_parameter +from semantic_kernel.plugin_definition import kernel_function, kernel_function_context_parameter from semantic_kernel.plugin_definition.function_view import FunctionView from semantic_kernel.plugin_definition.parameter_view import ParameterView @@ -37,7 +37,7 @@ class ActionPlanner: config: ActionPlannerConfig _stop_sequence: str = "#END-OF-PLAN" - _planner_function: SKFunctionBase + _planner_function: KernelFunctionBase _kernel: Kernel _prompt_template: str @@ -163,9 +163,9 @@ async def create_plan_async(self, goal: str) -> Plan: return plan - @sk_function(description="List a few good examples of plans to generate", name="GoodExamples") - @sk_function_context_parameter(name="goal", description="The current goal processed by the planner") - def good_examples(self, goal: str, context: SKContext) -> str: + @kernel_function(description="List a few good examples of plans to generate", name="GoodExamples") + @kernel_function_context_parameter(name="goal", description="The current goal processed by the planner") + def good_examples(self, goal: str, context: KernelContext) -> str: return dedent( """ [EXAMPLE] @@ -196,12 +196,12 @@ def good_examples(self, goal: str, context: SKContext) -> str: """ ) - @sk_function( + @kernel_function( description="List a few edge case examples of plans to handle", name="EdgeCaseExamples", ) - @sk_function_context_parameter(name="goal", description="The current goal processed by the planner") - def edge_case_examples(self, goal: str, context: SKContext) -> str: + @kernel_function_context_parameter(name="goal", description="The current goal processed by the planner") + def edge_case_examples(self, goal: str, context: KernelContext) -> str: return dedent( ''' [EXAMPLE] @@ -230,9 +230,9 @@ def edge_case_examples(self, goal: str, context: SKContext) -> str: ''' ) - @sk_function(description="List all functions available in the kernel", name="ListOfFunctions") - @sk_function_context_parameter(name="goal", description="The current goal processed by the planner") - def list_of_functions(self, goal: str, context: SKContext) -> str: + @kernel_function(description="List all functions available in the kernel", name="ListOfFunctions") + @kernel_function_context_parameter(name="goal", description="The current goal processed by the planner") + def list_of_functions(self, goal: str, context: KernelContext) -> str: if context.plugins is None: raise PlanningException( error_code=PlanningException.ErrorCodes.InvalidConfiguration, diff --git a/python/semantic_kernel/planning/basic_planner.py b/python/semantic_kernel/planning/basic_planner.py index feb8a0e35a17..5c83a3f13af0 100644 --- a/python/semantic_kernel/planning/basic_planner.py +++ b/python/semantic_kernel/planning/basic_planner.py @@ -212,17 +212,17 @@ async def execute_plan_async(self, plan: Plan, kernel: Kernel) -> str: for subtask in subtasks: plugin_name, function_name = subtask["function"].split(".") - sk_function = kernel.plugins.get_function(plugin_name, function_name) + kernel_function = kernel.plugins.get_function(plugin_name, function_name) # Get the arguments dictionary for the function args = subtask.get("args", None) if args: for key, value in args.items(): context[key] = value - output = await sk_function.invoke_async(variables=context) + output = await kernel_function.invoke_async(variables=context) else: - output = await sk_function.invoke_async(variables=context) + output = await kernel_function.invoke_async(variables=context) # Override the input context variable with the output of the function context["input"] = output.result diff --git a/python/semantic_kernel/planning/plan.py b/python/semantic_kernel/planning/plan.py index 2bcec0c076ce..24a1283e9eb8 100644 --- a/python/semantic_kernel/planning/plan.py +++ b/python/semantic_kernel/planning/plan.py @@ -17,8 +17,8 @@ from semantic_kernel.memory.null_memory import NullMemory from semantic_kernel.memory.semantic_text_memory_base import SemanticTextMemoryBase from semantic_kernel.orchestration.context_variables import ContextVariables -from semantic_kernel.orchestration.sk_context import SKContext -from semantic_kernel.orchestration.sk_function_base import SKFunctionBase +from semantic_kernel.orchestration.kernel_context import KernelContext +from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase from semantic_kernel.plugin_definition.function_view import FunctionView from semantic_kernel.plugin_definition.read_only_plugin_collection import ( ReadOnlyPluginCollection, @@ -30,10 +30,10 @@ logger: logging.Logger = logging.getLogger(__name__) -class Plan(SKFunctionBase): +class Plan(KernelFunctionBase): _state: ContextVariables = PrivateAttr() _steps: List["Plan"] = PrivateAttr() - _function: SKFunctionBase = PrivateAttr() + _function: KernelFunctionBase = PrivateAttr() _parameters: ContextVariables = PrivateAttr() _outputs: List[str] = PrivateAttr() _has_next_step: bool = PrivateAttr() @@ -102,7 +102,7 @@ def __init__( parameters: Optional[ContextVariables] = None, outputs: Optional[List[str]] = None, steps: Optional[List["Plan"]] = None, - function: Optional[SKFunctionBase] = None, + function: Optional[KernelFunctionBase] = None, ) -> None: super().__init__() self._name = "" if name is None else name @@ -126,7 +126,7 @@ def from_goal(cls, goal: str) -> "Plan": return cls(description=goal, plugin_name=cls.__name__) @classmethod - def from_function(cls, function: SKFunctionBase) -> "Plan": + def from_function(cls, function: KernelFunctionBase) -> "Plan": plan = cls() plan.set_function(function) return plan @@ -134,19 +134,19 @@ def from_function(cls, function: SKFunctionBase) -> "Plan": async def invoke_async( self, input: Optional[str] = None, - context: Optional[SKContext] = None, + context: Optional[KernelContext] = None, settings: Optional[AIRequestSettings] = None, memory: Optional[SemanticTextMemoryBase] = None, **kwargs, # TODO: cancellation_token: CancellationToken, - ) -> SKContext: + ) -> KernelContext: if kwargs.get("logger"): logger.warning("The `logger` parameter is deprecated. Please use the `logging` module instead.") if input is not None and input != "": self._state.update(input) if context is None: - context = SKContext( + context = KernelContext( variables=self._state, plugin_collection=ReadOnlyPluginCollection(), memory=memory or NullMemory(), @@ -175,18 +175,18 @@ async def invoke_async( def invoke( self, input: Optional[str] = None, - context: Optional[SKContext] = None, + context: Optional[KernelContext] = None, settings: Optional[AIRequestSettings] = None, memory: Optional[SemanticTextMemoryBase] = None, **kwargs, - ) -> SKContext: + ) -> KernelContext: if kwargs.get("logger"): logger.warning("The `logger` parameter is deprecated. Please use the `logging` module instead.") if input is not None and input != "": self._state.update(input) if context is None: - context = SKContext( + context = KernelContext( variables=self._state, plugin_collection=ReadOnlyPluginCollection(), memory=memory or NullMemory(), @@ -225,18 +225,18 @@ def invoke( def set_ai_configuration( self, settings: AIRequestSettings, - ) -> SKFunctionBase: + ) -> KernelFunctionBase: if self._function is not None: self._function.set_ai_configuration(settings) - def set_ai_service(self, service: Callable[[], TextCompletionClientBase]) -> SKFunctionBase: + def set_ai_service(self, service: Callable[[], TextCompletionClientBase]) -> KernelFunctionBase: if self._function is not None: self._function.set_ai_service(service) def set_default_plugin_collection( self, plugins: ReadOnlyPluginCollectionBase, - ) -> SKFunctionBase: + ) -> KernelFunctionBase: if self._function is not None: self._function.set_default_plugin_collection(plugins) @@ -245,7 +245,7 @@ def describe(self) -> Optional[FunctionView]: return self._function.describe() return None - def set_available_functions(self, plan: "Plan", context: SKContext) -> "Plan": + def set_available_functions(self, plan: "Plan", context: KernelContext) -> "Plan": if len(plan.steps) == 0: if context.plugins is None: raise KernelException( @@ -263,7 +263,7 @@ def set_available_functions(self, plan: "Plan", context: SKContext) -> "Plan": return plan - def add_steps(self, steps: Union[List["Plan"], List[SKFunctionBase]]) -> None: + def add_steps(self, steps: Union[List["Plan"], List[KernelFunctionBase]]) -> None: for step in steps: if type(step) is Plan: self._steps.append(step) @@ -281,7 +281,7 @@ def add_steps(self, steps: Union[List["Plan"], List[SKFunctionBase]]) -> None: new_step.set_function(step) self._steps.append(new_step) - def set_function(self, function: SKFunctionBase) -> None: + def set_function(self, function: KernelFunctionBase) -> None: self._function = function self._name = function.name self._plugin_name = function.plugin_name @@ -297,7 +297,7 @@ async def run_next_step_async( context = kernel.create_new_context(variables) return await self.invoke_next_step(context) - async def invoke_next_step(self, context: SKContext) -> "Plan": + async def invoke_next_step(self, context: KernelContext) -> "Plan": if self.has_next_step: step = self._steps[self._next_step_index] @@ -305,7 +305,7 @@ async def invoke_next_step(self, context: SKContext) -> "Plan": variables = self.get_next_step_variables(context.variables, step) # Invoke the step - func_context = SKContext( + func_context = KernelContext( variables=variables, memory=context.memory, plugin_collection=context.plugins, @@ -342,12 +342,12 @@ async def invoke_next_step(self, context: SKContext) -> "Plan": return self - def add_variables_to_context(self, variables: ContextVariables, context: SKContext) -> None: + def add_variables_to_context(self, variables: ContextVariables, context: KernelContext) -> None: for key in variables.variables: if key not in context.variables: context.variables.set(key, variables[key]) - def update_context_with_outputs(self, context: SKContext) -> None: + def update_context_with_outputs(self, context: KernelContext) -> None: result_string = "" if Plan.DEFAULT_RESULT_KEY in self._state.variables: result_string = self._state[Plan.DEFAULT_RESULT_KEY] @@ -367,7 +367,7 @@ def update_context_with_outputs(self, context: SKContext) -> None: def get_next_step_variables(self, variables: ContextVariables, step: "Plan") -> ContextVariables: # Priority for Input # - Parameters (expand from variables if needed) - # - SKContext.Variables + # - KernelContext.Variables # - Plan.State # - Empty if sending to another plan # - Plan.Description diff --git a/python/semantic_kernel/planning/sequential_planner/sequential_planner.py b/python/semantic_kernel/planning/sequential_planner/sequential_planner.py index efe96986fae1..52da36ce4249 100644 --- a/python/semantic_kernel/planning/sequential_planner/sequential_planner.py +++ b/python/semantic_kernel/planning/sequential_planner/sequential_planner.py @@ -10,7 +10,7 @@ SequentialPlannerConfig, ) from semantic_kernel.planning.sequential_planner.sequential_planner_extensions import ( - SequentialPlannerSKContextExtension as SKContextExtension, + SequentialPlannerKernelContextExtension as KernelContextExtension, ) from semantic_kernel.planning.sequential_planner.sequential_planner_parser import ( SequentialPlanParser, @@ -24,8 +24,8 @@ ) if TYPE_CHECKING: - from semantic_kernel.orchestration.sk_context import SKContext - from semantic_kernel.orchestration.sk_function_base import SKFunctionBase + from semantic_kernel.orchestration.kernel_context import KernelContext + from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase SEQUENTIAL_PLANNER_DEFAULT_DESCRIPTION = ( "Given a request or command or goal generate a step by step plan to " @@ -46,8 +46,8 @@ class SequentialPlanner: RESTRICTED_PLUGIN_NAME = "SequentialPlanner_Excluded" config: SequentialPlannerConfig - _context: "SKContext" - _function_flow_function: "SKFunctionBase" + _context: "KernelContext" + _function_flow_function: "KernelFunctionBase" def __init__(self, kernel: Kernel, config: SequentialPlannerConfig = None, prompt: str = None): assert isinstance(kernel, Kernel) @@ -81,7 +81,9 @@ async def create_plan_async(self, goal: str) -> Plan: if len(goal) == 0: raise PlanningException(PlanningException.ErrorCodes.InvalidGoal, "The goal specified is empty") - relevant_function_manual = await SKContextExtension.get_functions_manual_async(self._context, goal, self.config) + relevant_function_manual = await KernelContextExtension.get_functions_manual_async( + self._context, goal, self.config + ) self._context.variables.set("available_functions", relevant_function_manual) self._context.variables.update(goal) diff --git a/python/semantic_kernel/planning/sequential_planner/sequential_planner_extensions.py b/python/semantic_kernel/planning/sequential_planner/sequential_planner_extensions.py index 481e8f0d9569..f06cff2d812d 100644 --- a/python/semantic_kernel/planning/sequential_planner/sequential_planner_extensions.py +++ b/python/semantic_kernel/planning/sequential_planner/sequential_planner_extensions.py @@ -7,7 +7,7 @@ from semantic_kernel.kernel_exception import KernelException from semantic_kernel.memory.memory_query_result import MemoryQueryResult from semantic_kernel.memory.null_memory import NullMemory -from semantic_kernel.orchestration.sk_context import SKContext +from semantic_kernel.orchestration.kernel_context import KernelContext from semantic_kernel.planning.sequential_planner.sequential_planner_config import ( SequentialPlannerConfig, ) @@ -40,20 +40,20 @@ def to_embedding_string(function: FunctionView): return f"{function.name}:\n description: {function.description}\n " f" inputs:\n{inputs}" -class SequentialPlannerSKContextExtension: - PLANNER_MEMORY_COLLECTION_NAME = " Planning.SKFunctionManual" - PLAN_SK_FUNCTIONS_ARE_REMEMBERED = "Planning.SKFunctionsAreRemembered" +class SequentialPlannerKernelContextExtension: + PLANNER_MEMORY_COLLECTION_NAME = " Planning.KernelFunctionManual" + PLAN_KERNEL_FUNCTIONS_ARE_REMEMBERED = "Planning.KernelFunctionsAreRemembered" @staticmethod async def get_functions_manual_async( - context: SKContext, + context: KernelContext, semantic_query: str = None, config: SequentialPlannerConfig = None, ) -> str: config = config or SequentialPlannerConfig() if config.get_available_functions_async is None: - functions = await SequentialPlannerSKContextExtension.get_available_functions_async( + functions = await SequentialPlannerKernelContextExtension.get_available_functions_async( context, config, semantic_query ) else: @@ -63,7 +63,7 @@ async def get_functions_manual_async( @staticmethod async def get_available_functions_async( - context: SKContext, + context: KernelContext, config: SequentialPlannerConfig, semantic_query: str = None, ): @@ -97,18 +97,18 @@ async def get_available_functions_async( return available_functions # Remember functions in memory so that they can be searched. - await SequentialPlannerSKContextExtension.remember_functions_async(context, available_functions) + await SequentialPlannerKernelContextExtension.remember_functions_async(context, available_functions) # Search for functions that match the semantic query. memories = await context.memory.search_async( - SequentialPlannerSKContextExtension.PLANNER_MEMORY_COLLECTION_NAME, + SequentialPlannerKernelContextExtension.PLANNER_MEMORY_COLLECTION_NAME, semantic_query, config.max_relevant_functions, config.relevancy_threshold, ) # Add functions that were found in the search results. - relevant_functions = await SequentialPlannerSKContextExtension.get_relevant_functions_async( + relevant_functions = await SequentialPlannerKernelContextExtension.get_relevant_functions_async( context, available_functions, memories ) @@ -123,7 +123,7 @@ async def get_available_functions_async( @staticmethod async def get_relevant_functions_async( - context: SKContext, + context: KernelContext, available_functions: List[FunctionView], memories: AsyncIterable[MemoryQueryResult], ) -> List[FunctionView]: @@ -150,9 +150,9 @@ async def get_relevant_functions_async( return relevant_functions @staticmethod - async def remember_functions_async(context: SKContext, available_functions: List[FunctionView]): + async def remember_functions_async(context: KernelContext, available_functions: List[FunctionView]): # Check if the functions have already been saved to memory. - if SequentialPlannerSKContextExtension.PLAN_SK_FUNCTIONS_ARE_REMEMBERED in context.variables: + if SequentialPlannerKernelContextExtension.PLAN_KERNEL_FUNCTIONS_ARE_REMEMBERED in context.variables: return for function in available_functions: @@ -163,7 +163,7 @@ async def remember_functions_async(context: SKContext, available_functions: List # It'd be nice if there were a saveIfNotExists method on the memory interface memory_entry = await context.memory.get_async( - collection=SequentialPlannerSKContextExtension.PLANNER_MEMORY_COLLECTION_NAME, + collection=SequentialPlannerKernelContextExtension.PLANNER_MEMORY_COLLECTION_NAME, key=key, with_embedding=False, ) @@ -172,7 +172,7 @@ async def remember_functions_async(context: SKContext, available_functions: List # As folks may want to tune their functions to be more or less relevant. # Memory now supports these such strategies. await context.memory.save_information_async( - collection=SequentialPlannerSKContextExtension.PLANNER_MEMORY_COLLECTION_NAME, + collection=SequentialPlannerKernelContextExtension.PLANNER_MEMORY_COLLECTION_NAME, text=text_to_embed, id=key, description=description, @@ -180,4 +180,4 @@ async def remember_functions_async(context: SKContext, available_functions: List ) # Set a flag to indicate that the functions have been saved to memory. - context.variables.set(SequentialPlannerSKContextExtension.PLAN_SK_FUNCTIONS_ARE_REMEMBERED, "true") + context.variables.set(SequentialPlannerKernelContextExtension.PLAN_KERNEL_FUNCTIONS_ARE_REMEMBERED, "true") diff --git a/python/semantic_kernel/planning/sequential_planner/sequential_planner_parser.py b/python/semantic_kernel/planning/sequential_planner/sequential_planner_parser.py index 0071eb7e3930..072cd9e976a8 100644 --- a/python/semantic_kernel/planning/sequential_planner/sequential_planner_parser.py +++ b/python/semantic_kernel/planning/sequential_planner/sequential_planner_parser.py @@ -6,8 +6,8 @@ from semantic_kernel.kernel_exception import KernelException from semantic_kernel.orchestration.context_variables import ContextVariables -from semantic_kernel.orchestration.sk_context import SKContext -from semantic_kernel.orchestration.sk_function_base import SKFunctionBase +from semantic_kernel.orchestration.kernel_context import KernelContext +from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase from semantic_kernel.planning.plan import Plan from semantic_kernel.planning.planning_exception import PlanningException @@ -22,9 +22,9 @@ class SequentialPlanParser: @staticmethod def get_plugin_function( - context: SKContext, - ) -> Callable[[str, str], Optional[SKFunctionBase]]: - def function(plugin_name: str, function_name: str) -> Optional[SKFunctionBase]: + context: KernelContext, + ) -> Callable[[str, str], Optional[KernelFunctionBase]]: + def function(plugin_name: str, function_name: str) -> Optional[KernelFunctionBase]: try: return context.plugins.get_function(plugin_name, function_name) except KernelException: @@ -36,7 +36,7 @@ def function(plugin_name: str, function_name: str) -> Optional[SKFunctionBase]: def to_plan_from_xml( xml_string: str, goal: str, - get_plugin_function: Callable[[str, str], Optional[SKFunctionBase]], + get_plugin_function: Callable[[str, str], Optional[KernelFunctionBase]], allow_missing_functions: bool = False, ): xml_string = "" + xml_string + "" diff --git a/python/semantic_kernel/planning/stepwise_planner/stepwise_planner.py b/python/semantic_kernel/planning/stepwise_planner/stepwise_planner.py index 3ac6358f5896..93c2df105201 100644 --- a/python/semantic_kernel/planning/stepwise_planner/stepwise_planner.py +++ b/python/semantic_kernel/planning/stepwise_planner/stepwise_planner.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING, Dict, List from semantic_kernel.kernel import Kernel -from semantic_kernel.orchestration.sk_context import SKContext +from semantic_kernel.orchestration.kernel_context import KernelContext from semantic_kernel.planning.plan import Plan from semantic_kernel.planning.planning_exception import PlanningException from semantic_kernel.planning.stepwise_planner.stepwise_planner_config import ( @@ -17,10 +17,10 @@ ) from semantic_kernel.planning.stepwise_planner.system_step import SystemStep from semantic_kernel.plugin_definition.function_view import FunctionView -from semantic_kernel.plugin_definition.sk_function_context_parameter_decorator import ( - sk_function_context_parameter, +from semantic_kernel.plugin_definition.kernel_function_context_parameter_decorator import ( + kernel_function_context_parameter, ) -from semantic_kernel.plugin_definition.sk_function_decorator import sk_function +from semantic_kernel.plugin_definition.kernel_function_decorator import kernel_function from semantic_kernel.semantic_functions.prompt_template import PromptTemplate from semantic_kernel.semantic_functions.prompt_template_config import ( PromptTemplateConfig, @@ -30,7 +30,7 @@ ) if TYPE_CHECKING: - from semantic_kernel.orchestration.sk_function_base import SKFunctionBase + from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase logger: logging.Logger = logging.getLogger(__name__) @@ -64,8 +64,8 @@ def is_null_or_empty(value: str) -> bool: class StepwisePlanner: config: StepwisePlannerConfig - _context: "SKContext" - _function_flow_function: "SKFunctionBase" + _context: "KernelContext" + _function_flow_function: "KernelFunctionBase" def __init__( self, @@ -117,10 +117,10 @@ def create_plan(self, goal: str) -> Plan: return plan # TODO: sync C# with https://github.com/microsoft/semantic-kernel/pull/1195 - @sk_function(name="ExecutePlan", description="Execute a plan") - @sk_function_context_parameter(name="question", description="The question to answer") - @sk_function_context_parameter(name="function_descriptions", description="List of tool descriptions") - async def execute_plan_async(self, context: SKContext) -> SKContext: + @kernel_function(name="ExecutePlan", description="Execute a plan") + @kernel_function_context_parameter(name="question", description="The question to answer") + @kernel_function_context_parameter(name="function_descriptions", description="List of tool descriptions") + async def execute_plan_async(self, context: KernelContext) -> KernelContext: question = context["question"] steps_taken: List[SystemStep] = [] @@ -239,7 +239,7 @@ def parse_result(self, input: str): return result - def add_execution_stats_to_context(self, steps_taken: List[SystemStep], context: SKContext): + def add_execution_stats_to_context(self, steps_taken: List[SystemStep], context: KernelContext): context.variables.set("step_count", str(len(steps_taken))) context.variables.set("steps_taken", json.dumps([s.__dict__ for s in steps_taken], indent=4)) @@ -333,7 +333,7 @@ async def invoke_action_async(self, action_name: str, action_variables: Dict[str f"{target_function.plugin_name}.{target_function.name}. Error: {e}", ) - def create_action_context(self, action_variables: Dict[str, str]) -> SKContext: + def create_action_context(self, action_variables: Dict[str, str]) -> KernelContext: action_context = self._kernel.create_new_context() if action_variables is not None: for k, v in action_variables.items(): @@ -373,7 +373,7 @@ def import_semantic_function( function_name: str, prompt_template: str, config: PromptTemplateConfig = None, - ) -> "SKFunctionBase": + ) -> "KernelFunctionBase": template = PromptTemplate(prompt_template, kernel.prompt_template_engine, config) function_config = SemanticFunctionConfig(config, template) diff --git a/python/semantic_kernel/plugin_definition/__init__.py b/python/semantic_kernel/plugin_definition/__init__.py index 38bbe18a3941..f7408f8552bb 100644 --- a/python/semantic_kernel/plugin_definition/__init__.py +++ b/python/semantic_kernel/plugin_definition/__init__.py @@ -1,10 +1,10 @@ # Copyright (c) Microsoft. All rights reserved. -from semantic_kernel.plugin_definition.sk_function_context_parameter_decorator import ( - sk_function_context_parameter, +from semantic_kernel.plugin_definition.kernel_function_context_parameter_decorator import ( + kernel_function_context_parameter, ) -from semantic_kernel.plugin_definition.sk_function_decorator import sk_function +from semantic_kernel.plugin_definition.kernel_function_decorator import kernel_function __all__ = [ - "sk_function", - "sk_function_context_parameter", + "kernel_function", + "kernel_function_context_parameter", ] diff --git a/python/semantic_kernel/plugin_definition/function_view.py b/python/semantic_kernel/plugin_definition/function_view.py index 9f8b9ace6bdc..3b697397b8fd 100644 --- a/python/semantic_kernel/plugin_definition/function_view.py +++ b/python/semantic_kernel/plugin_definition/function_view.py @@ -2,12 +2,12 @@ from typing import List +from semantic_kernel.kernel_pydantic import KernelBaseModel from semantic_kernel.plugin_definition.parameter_view import ParameterView -from semantic_kernel.sk_pydantic import SKBaseModel from semantic_kernel.utils.validation import validate_function_name -class FunctionView(SKBaseModel): +class FunctionView(KernelBaseModel): name: str plugin_name: str description: str diff --git a/python/semantic_kernel/plugin_definition/functions_view.py b/python/semantic_kernel/plugin_definition/functions_view.py index 53d2af6ec2f2..52dde0ad828f 100644 --- a/python/semantic_kernel/plugin_definition/functions_view.py +++ b/python/semantic_kernel/plugin_definition/functions_view.py @@ -5,11 +5,11 @@ from pydantic import Field from semantic_kernel.kernel_exception import KernelException +from semantic_kernel.kernel_pydantic import KernelBaseModel from semantic_kernel.plugin_definition.function_view import FunctionView -from semantic_kernel.sk_pydantic import SKBaseModel -class FunctionsView(SKBaseModel): +class FunctionsView(KernelBaseModel): semantic_functions: Dict[str, List[FunctionView]] = Field(default_factory=dict) native_functions: Dict[str, List[FunctionView]] = Field(default_factory=dict) diff --git a/python/semantic_kernel/plugin_definition/sk_function_context_parameter_decorator.py b/python/semantic_kernel/plugin_definition/kernel_function_context_parameter_decorator.py similarity index 73% rename from python/semantic_kernel/plugin_definition/sk_function_context_parameter_decorator.py rename to python/semantic_kernel/plugin_definition/kernel_function_context_parameter_decorator.py index 826521d8dc42..bc60e1a01b51 100644 --- a/python/semantic_kernel/plugin_definition/sk_function_context_parameter_decorator.py +++ b/python/semantic_kernel/plugin_definition/kernel_function_context_parameter_decorator.py @@ -1,11 +1,11 @@ # Copyright (c) Microsoft. All rights reserved. -def sk_function_context_parameter( +def kernel_function_context_parameter( *, name: str, description: str, default_value: str = "", type: str = "string", required: bool = False ): """ - Decorator for SK function context parameters. + Decorator for kernel function context parameters. Args: name -- The name of the context parameter @@ -17,10 +17,10 @@ def sk_function_context_parameter( """ def decorator(func): - if not hasattr(func, "__sk_function_context_parameters__"): - func.__sk_function_context_parameters__ = [] + if not hasattr(func, "__kernel_function_context_parameters__"): + func.__kernel_function_context_parameters__ = [] - func.__sk_function_context_parameters__.append( + func.__kernel_function_context_parameters__.append( { "name": name, "description": description, diff --git a/python/semantic_kernel/plugin_definition/sk_function_decorator.py b/python/semantic_kernel/plugin_definition/kernel_function_decorator.py similarity index 55% rename from python/semantic_kernel/plugin_definition/sk_function_decorator.py rename to python/semantic_kernel/plugin_definition/kernel_function_decorator.py index 6c56fbf85bad..949a07efb8e6 100644 --- a/python/semantic_kernel/plugin_definition/sk_function_decorator.py +++ b/python/semantic_kernel/plugin_definition/kernel_function_decorator.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft. All rights reserved. -def sk_function( +def kernel_function( *, description: str = "", name: str = "", @@ -9,7 +9,7 @@ def sk_function( input_default_value: str = "", ): """ - Decorator for SK functions. + Decorator for kernel functions. Args: description -- The description of the function @@ -19,11 +19,11 @@ def sk_function( """ def decorator(func): - func.__sk_function__ = True - func.__sk_function_description__ = description or "" - func.__sk_function_name__ = name or func.__name__ - func.__sk_function_input_description__ = input_description or "" - func.__sk_function_input_default_value__ = input_default_value or "" + func.__kernel_function__ = True + func.__kernel_function_description__ = description or "" + func.__kernel_function_name__ = name or func.__name__ + func.__kernel_function_input_description__ = input_description or "" + func.__kernel_function_input_default_value__ = input_default_value or "" return func return decorator diff --git a/python/semantic_kernel/plugin_definition/parameter_view.py b/python/semantic_kernel/plugin_definition/parameter_view.py index f0ec3ee9fc8f..1b3058bce588 100644 --- a/python/semantic_kernel/plugin_definition/parameter_view.py +++ b/python/semantic_kernel/plugin_definition/parameter_view.py @@ -5,11 +5,11 @@ from pydantic import Field, field_validator -from semantic_kernel.sk_pydantic import SKBaseModel +from semantic_kernel.kernel_pydantic import KernelBaseModel from semantic_kernel.utils.validation import validate_function_param_name -class ParameterView(SKBaseModel): +class ParameterView(KernelBaseModel): name: str description: str default_value: str diff --git a/python/semantic_kernel/plugin_definition/plugin_collection.py b/python/semantic_kernel/plugin_definition/plugin_collection.py index 8792d5a56fe3..0823822093c8 100644 --- a/python/semantic_kernel/plugin_definition/plugin_collection.py +++ b/python/semantic_kernel/plugin_definition/plugin_collection.py @@ -5,7 +5,7 @@ from pydantic import Field -from semantic_kernel.orchestration.sk_function import SKFunction +from semantic_kernel.orchestration.kernel_function import KernelFunction from semantic_kernel.plugin_definition import constants from semantic_kernel.plugin_definition.functions_view import FunctionsView from semantic_kernel.plugin_definition.plugin_collection_base import ( @@ -19,7 +19,7 @@ ) if TYPE_CHECKING: - from semantic_kernel.orchestration.sk_function_base import SKFunctionBase + from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase logger: logging.Logger = logging.getLogger(__name__) @@ -31,7 +31,7 @@ class PluginCollection(PluginCollectionBase): def __init__( self, log: Optional[Any] = None, - plugin_collection: Union[Dict[str, Dict[str, SKFunction]], None] = None, + plugin_collection: Union[Dict[str, Dict[str, KernelFunction]], None] = None, read_only_plugin_collection_: Optional[ReadOnlyPluginCollection] = None, ) -> None: if log: @@ -54,7 +54,7 @@ def read_only_plugin_collection(self) -> ReadOnlyPluginCollectionBase: def plugin_collection(self): return self.read_only_plugin_collection_.data - def add_semantic_function(self, function: "SKFunctionBase") -> None: + def add_semantic_function(self, function: "KernelFunctionBase") -> None: if function is None: raise ValueError("The function provided cannot be `None`") @@ -63,7 +63,7 @@ def add_semantic_function(self, function: "SKFunctionBase") -> None: self.plugin_collection.setdefault(s_name, {})[f_name] = function - def add_native_function(self, function: "SKFunctionBase") -> None: + def add_native_function(self, function: "KernelFunctionBase") -> None: if function is None: raise ValueError("The function provided cannot be `None`") @@ -81,14 +81,14 @@ def has_semantic_function(self, plugin_name: Optional[str], function_name: str) def has_native_function(self, plugin_name: Optional[str], function_name: str) -> bool: return self.read_only_plugin_collection_.has_native_function(plugin_name, function_name) - def get_semantic_function(self, plugin_name: Optional[str], function_name: str) -> "SKFunctionBase": + def get_semantic_function(self, plugin_name: Optional[str], function_name: str) -> "KernelFunctionBase": return self.read_only_plugin_collection_.get_semantic_function(plugin_name, function_name) - def get_native_function(self, plugin_name: Optional[str], function_name: str) -> "SKFunctionBase": + def get_native_function(self, plugin_name: Optional[str], function_name: str) -> "KernelFunctionBase": return self.read_only_plugin_collection_.get_native_function(plugin_name, function_name) def get_functions_view(self, include_semantic: bool = True, include_native: bool = True) -> FunctionsView: return self.read_only_plugin_collection_.get_functions_view(include_semantic, include_native) - def get_function(self, plugin_name: Optional[str], function_name: str) -> "SKFunctionBase": + def get_function(self, plugin_name: Optional[str], function_name: str) -> "KernelFunctionBase": return self.read_only_plugin_collection_.get_function(plugin_name, function_name) diff --git a/python/semantic_kernel/plugin_definition/plugin_collection_base.py b/python/semantic_kernel/plugin_definition/plugin_collection_base.py index 9251211e6a32..af4621f56f16 100644 --- a/python/semantic_kernel/plugin_definition/plugin_collection_base.py +++ b/python/semantic_kernel/plugin_definition/plugin_collection_base.py @@ -8,7 +8,7 @@ ) if TYPE_CHECKING: - from semantic_kernel.orchestration.sk_function_base import SKFunctionBase + from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase PluginCollectionT = TypeVar("PluginCollectionT", bound="PluginCollectionBase") @@ -21,9 +21,9 @@ def read_only_plugin_collection(self) -> ReadOnlyPluginCollectionBase: pass @abstractmethod - def add_semantic_function(self, semantic_function: "SKFunctionBase") -> "PluginCollectionBase": + def add_semantic_function(self, semantic_function: "KernelFunctionBase") -> "PluginCollectionBase": pass @abstractmethod - def add_native_function(self, native_function: "SKFunctionBase") -> "PluginCollectionBase": + def add_native_function(self, native_function: "KernelFunctionBase") -> "PluginCollectionBase": pass diff --git a/python/semantic_kernel/plugin_definition/read_only_plugin_collection.py b/python/semantic_kernel/plugin_definition/read_only_plugin_collection.py index 8995e185dc4e..00d08062b987 100644 --- a/python/semantic_kernel/plugin_definition/read_only_plugin_collection.py +++ b/python/semantic_kernel/plugin_definition/read_only_plugin_collection.py @@ -6,7 +6,7 @@ from pydantic import ConfigDict, Field from semantic_kernel.kernel_exception import KernelException -from semantic_kernel.orchestration.sk_function import SKFunction +from semantic_kernel.orchestration.kernel_function import KernelFunction from semantic_kernel.plugin_definition import constants from semantic_kernel.plugin_definition.functions_view import FunctionsView from semantic_kernel.plugin_definition.read_only_plugin_collection_base import ( @@ -14,19 +14,19 @@ ) if TYPE_CHECKING: - from semantic_kernel.orchestration.sk_function_base import SKFunctionBase + from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase logger: logging.Logger = logging.getLogger(__name__) class ReadOnlyPluginCollection(ReadOnlyPluginCollectionBase): GLOBAL_PLUGIN: ClassVar[str] = constants.GLOBAL_PLUGIN - data: Dict[str, Dict[str, SKFunction]] = Field(default_factory=dict) + data: Dict[str, Dict[str, KernelFunction]] = Field(default_factory=dict) model_config = ConfigDict(frozen=False) def __init__( self, - data: Dict[str, Dict[str, SKFunction]] = None, + data: Dict[str, Dict[str, KernelFunction]] = None, log: Optional[Any] = None, ) -> None: super().__init__(data=data or {}) @@ -56,7 +56,7 @@ def has_native_function(self, plugin_name: str, function_name: str) -> bool: return False return self.data[s_name][f_name].is_native - def get_semantic_function(self, plugin_name: str, function_name: str) -> "SKFunctionBase": + def get_semantic_function(self, plugin_name: str, function_name: str) -> "KernelFunctionBase": s_name, f_name = self._normalize_names(plugin_name, function_name) if self.has_semantic_function(s_name, f_name): return self.data[s_name][f_name] @@ -67,7 +67,7 @@ def get_semantic_function(self, plugin_name: str, function_name: str) -> "SKFunc f"Function not available: {s_name}.{f_name}", ) - def get_native_function(self, plugin_name: str, function_name: str) -> "SKFunctionBase": + def get_native_function(self, plugin_name: str, function_name: str) -> "KernelFunctionBase": s_name, f_name = self._normalize_names(plugin_name, function_name, True) if self.has_native_function(s_name, f_name): return self.data[s_name][f_name] @@ -90,7 +90,7 @@ def get_functions_view(self, include_semantic: bool = True, include_native: bool return result - def get_function(self, plugin_name: Optional[str], function_name: str) -> "SKFunctionBase": + def get_function(self, plugin_name: Optional[str], function_name: str) -> "KernelFunctionBase": s_name, f_name = self._normalize_names(plugin_name, function_name, True) if self.has_function(s_name, f_name): return self.data[s_name][f_name] diff --git a/python/semantic_kernel/plugin_definition/read_only_plugin_collection_base.py b/python/semantic_kernel/plugin_definition/read_only_plugin_collection_base.py index 7300742c3f13..101f80ad2816 100644 --- a/python/semantic_kernel/plugin_definition/read_only_plugin_collection_base.py +++ b/python/semantic_kernel/plugin_definition/read_only_plugin_collection_base.py @@ -3,14 +3,14 @@ from abc import ABC, abstractmethod from typing import TYPE_CHECKING, Optional -from semantic_kernel.sk_pydantic import SKBaseModel +from semantic_kernel.kernel_pydantic import KernelBaseModel if TYPE_CHECKING: - from semantic_kernel.orchestration.sk_function_base import SKFunctionBase + from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase from semantic_kernel.plugin_definition.functions_view import FunctionsView -class ReadOnlyPluginCollectionBase(SKBaseModel, ABC): +class ReadOnlyPluginCollectionBase(KernelBaseModel, ABC): @abstractmethod def has_function(self, plugin_name: Optional[str], function_name: str) -> bool: pass @@ -24,11 +24,11 @@ def has_native_function(self, plugin_name: Optional[str], function_name: str) -> pass @abstractmethod - def get_semantic_function(self, plugin_name: Optional[str], function_name: str) -> "SKFunctionBase": + def get_semantic_function(self, plugin_name: Optional[str], function_name: str) -> "KernelFunctionBase": pass @abstractmethod - def get_native_function(self, plugin_name: Optional[str], function_name: str) -> "SKFunctionBase": + def get_native_function(self, plugin_name: Optional[str], function_name: str) -> "KernelFunctionBase": pass @abstractmethod @@ -36,5 +36,5 @@ def get_functions_view(self, include_semantic: bool = True, include_native: bool pass @abstractmethod - def get_function(self, plugin_name: Optional[str], function_name: str) -> "SKFunctionBase": + def get_function(self, plugin_name: Optional[str], function_name: str) -> "KernelFunctionBase": pass diff --git a/python/semantic_kernel/reliability/pass_through_without_retry.py b/python/semantic_kernel/reliability/pass_through_without_retry.py index b36334be5a5a..c0728208d2be 100644 --- a/python/semantic_kernel/reliability/pass_through_without_retry.py +++ b/python/semantic_kernel/reliability/pass_through_without_retry.py @@ -3,15 +3,15 @@ import logging from typing import Any, Awaitable, Callable, Optional, TypeVar +from semantic_kernel.kernel_pydantic import KernelBaseModel from semantic_kernel.reliability.retry_mechanism_base import RetryMechanismBase -from semantic_kernel.sk_pydantic import SKBaseModel T = TypeVar("T") logger: logging.Logger = logging.getLogger(__name__) -class PassThroughWithoutRetry(RetryMechanismBase, SKBaseModel): +class PassThroughWithoutRetry(RetryMechanismBase, KernelBaseModel): """A retry mechanism that does not retry.""" async def execute_with_retry_async( diff --git a/python/semantic_kernel/semantic_functions/chat_prompt_template.py b/python/semantic_kernel/semantic_functions/chat_prompt_template.py index fa2adc610113..e12f28522f5d 100644 --- a/python/semantic_kernel/semantic_functions/chat_prompt_template.py +++ b/python/semantic_kernel/semantic_functions/chat_prompt_template.py @@ -16,7 +16,7 @@ ) if TYPE_CHECKING: - from semantic_kernel.orchestration.sk_context import SKContext + from semantic_kernel.orchestration.kernel_context import KernelContext ChatMessageT = TypeVar("ChatMessageT", bound=ChatMessage) @@ -46,7 +46,7 @@ def __init__( for message in self.prompt_config.execution_settings.messages: self.add_message(message["role"], message["content"]) - async def render_async(self, context: "SKContext") -> str: + async def render_async(self, context: "KernelContext") -> str: raise NotImplementedError( "Can't call render_async on a ChatPromptTemplate.\n" "Use render_messages_async instead." ) @@ -83,7 +83,7 @@ def add_message(self, role: str, message: Optional[str] = None, **kwargs: Any) - ) ) - async def render_messages_async(self, context: "SKContext") -> List[Dict[str, str]]: + async def render_messages_async(self, context: "KernelContext") -> List[Dict[str, str]]: """Render the content of the message in the chat template, based on the context.""" if len(self.messages) == 0 or self.messages[-1].role in [ "assistant", diff --git a/python/semantic_kernel/semantic_functions/prompt_template.py b/python/semantic_kernel/semantic_functions/prompt_template.py index 1de4b47f6422..67be5e476372 100644 --- a/python/semantic_kernel/semantic_functions/prompt_template.py +++ b/python/semantic_kernel/semantic_functions/prompt_template.py @@ -15,7 +15,7 @@ ) if TYPE_CHECKING: - from semantic_kernel.orchestration.sk_context import SKContext + from semantic_kernel.orchestration.kernel_context import KernelContext logger: logging.Logger = logging.getLogger(__name__) @@ -77,5 +77,5 @@ def get_parameters(self) -> List[ParameterView]: return result - async def render_async(self, context: "SKContext") -> str: + async def render_async(self, context: "KernelContext") -> str: return await self.template_engine.render_async(self.template, context) diff --git a/python/semantic_kernel/semantic_functions/prompt_template_base.py b/python/semantic_kernel/semantic_functions/prompt_template_base.py index 1bb74a49a271..82b9a9c80fd9 100644 --- a/python/semantic_kernel/semantic_functions/prompt_template_base.py +++ b/python/semantic_kernel/semantic_functions/prompt_template_base.py @@ -3,18 +3,18 @@ from abc import ABC, abstractmethod from typing import TYPE_CHECKING, List -from semantic_kernel.sk_pydantic import SKBaseModel +from semantic_kernel.kernel_pydantic import KernelBaseModel if TYPE_CHECKING: - from semantic_kernel.orchestration.sk_context import SKContext + from semantic_kernel.orchestration.kernel_context import KernelContext from semantic_kernel.plugin_definition.parameter_view import ParameterView -class PromptTemplateBase(SKBaseModel, ABC): +class PromptTemplateBase(KernelBaseModel, ABC): @abstractmethod def get_parameters(self) -> List["ParameterView"]: pass @abstractmethod - async def render_async(self, context: "SKContext") -> str: + async def render_async(self, context: "KernelContext") -> str: pass diff --git a/python/semantic_kernel/semantic_functions/prompt_template_config.py b/python/semantic_kernel/semantic_functions/prompt_template_config.py index ab866d9d4c80..7b0a324f06bb 100644 --- a/python/semantic_kernel/semantic_functions/prompt_template_config.py +++ b/python/semantic_kernel/semantic_functions/prompt_template_config.py @@ -5,13 +5,13 @@ from pydantic import Field from semantic_kernel.connectors.ai.ai_request_settings import AIRequestSettings +from semantic_kernel.kernel_pydantic import KernelBaseModel from semantic_kernel.plugin_definition.parameter_view import ParameterView -from semantic_kernel.sk_pydantic import SKBaseModel AIRequestSettingsT = TypeVar("AIRequestSettingsT", bound=AIRequestSettings) -class PromptTemplateConfig(SKBaseModel, Generic[AIRequestSettingsT]): +class PromptTemplateConfig(KernelBaseModel, Generic[AIRequestSettingsT]): schema_: int = Field(default=1, alias="schema") type: str = "completion" description: str = "" diff --git a/python/semantic_kernel/template_engine/blocks/block.py b/python/semantic_kernel/template_engine/blocks/block.py index 8ac0a2d9a29e..0f7169888ef4 100644 --- a/python/semantic_kernel/template_engine/blocks/block.py +++ b/python/semantic_kernel/template_engine/blocks/block.py @@ -3,13 +3,13 @@ import logging from typing import Any, Optional, Tuple -from semantic_kernel.sk_pydantic import SKBaseModel +from semantic_kernel.kernel_pydantic import KernelBaseModel from semantic_kernel.template_engine.blocks.block_types import BlockTypes logger: logging.Logger = logging.getLogger(__name__) -class Block(SKBaseModel): +class Block(KernelBaseModel): content: Optional[str] = None def __init__(self, content: Optional[str] = None, log: Optional[Any] = None) -> None: diff --git a/python/semantic_kernel/template_engine/blocks/code_block.py b/python/semantic_kernel/template_engine/blocks/code_block.py index 3b65ac95191b..146fbd5cf069 100644 --- a/python/semantic_kernel/template_engine/blocks/code_block.py +++ b/python/semantic_kernel/template_engine/blocks/code_block.py @@ -5,7 +5,7 @@ import pydantic as pdt -from semantic_kernel.orchestration.sk_function_base import SKFunctionBase +from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase from semantic_kernel.plugin_definition.read_only_plugin_collection_base import ( ReadOnlyPluginCollectionBase, ) @@ -117,7 +117,7 @@ async def _render_function_call_async(self, f_block: FunctionIdBlock, context): def _get_function_from_plugin_collection( self, plugins: ReadOnlyPluginCollectionBase, f_block: FunctionIdBlock - ) -> Optional[SKFunctionBase]: + ) -> Optional[KernelFunctionBase]: if not f_block.plugin_name and plugins.has_function(None, f_block.function_name): return plugins.get_function(None, f_block.function_name) diff --git a/python/semantic_kernel/template_engine/code_tokenizer.py b/python/semantic_kernel/template_engine/code_tokenizer.py index cdee11227d37..4341384cc612 100644 --- a/python/semantic_kernel/template_engine/code_tokenizer.py +++ b/python/semantic_kernel/template_engine/code_tokenizer.py @@ -3,7 +3,7 @@ import logging from typing import Any, List, Optional -from semantic_kernel.sk_pydantic import SKBaseModel +from semantic_kernel.kernel_pydantic import KernelBaseModel from semantic_kernel.template_engine.blocks.block import Block from semantic_kernel.template_engine.blocks.block_types import BlockTypes from semantic_kernel.template_engine.blocks.function_id_block import FunctionIdBlock @@ -22,7 +22,7 @@ # [value] ::= "'" [text] "'" | '"' [text] '"' # [function-call] ::= [function-id] | [function-id] [parameter] # [parameter] ::= [variable] | [value] -class CodeTokenizer(SKBaseModel): +class CodeTokenizer(KernelBaseModel): def __init__(self, log: Optional[Any] = None): super().__init__() diff --git a/python/semantic_kernel/template_engine/prompt_template_engine.py b/python/semantic_kernel/template_engine/prompt_template_engine.py index a15caecc73b9..15dd44674d70 100644 --- a/python/semantic_kernel/template_engine/prompt_template_engine.py +++ b/python/semantic_kernel/template_engine/prompt_template_engine.py @@ -5,20 +5,20 @@ from pydantic import PrivateAttr +from semantic_kernel.kernel_pydantic import KernelBaseModel from semantic_kernel.orchestration.context_variables import ContextVariables -from semantic_kernel.sk_pydantic import SKBaseModel from semantic_kernel.template_engine.blocks.block import Block from semantic_kernel.template_engine.blocks.block_types import BlockTypes from semantic_kernel.template_engine.protocols.text_renderer import TextRenderer from semantic_kernel.template_engine.template_tokenizer import TemplateTokenizer if TYPE_CHECKING: - from semantic_kernel.orchestration.sk_context import SKContext + from semantic_kernel.orchestration.kernel_context import KernelContext logger: logging.Logger = logging.getLogger(__name__) -class PromptTemplateEngine(SKBaseModel): +class PromptTemplateEngine(KernelBaseModel): _tokenizer: TemplateTokenizer = PrivateAttr() def __init__(self, **kwargs) -> None: @@ -50,7 +50,7 @@ def extract_blocks(self, template_text: Optional[str] = None, validate: bool = T return blocks - async def render_async(self, template_text: str, context: "SKContext") -> str: + async def render_async(self, template_text: str, context: "KernelContext") -> str: """ Given a prompt template, replace the variables with their values and execute the functions replacing their reference with the @@ -64,7 +64,7 @@ async def render_async(self, template_text: str, context: "SKContext") -> str: blocks = self.extract_blocks(template_text) return await self.render_blocks_async(blocks, context) - async def render_blocks_async(self, blocks: List[Block], context: "SKContext") -> str: + async def render_blocks_async(self, blocks: List[Block], context: "KernelContext") -> str: """ Given a list of blocks render each block and compose the final result. @@ -114,7 +114,7 @@ def render_variables(self, blocks: List[Block], variables: Optional[ContextVaria return rendered_blocks - async def render_code_async(self, blocks: List[Block], execution_context: "SKContext") -> List[Block]: + async def render_code_async(self, blocks: List[Block], execution_context: "KernelContext") -> List[Block]: """ Given a list of blocks, render the Code Blocks, executing the functions and replacing placeholders with the functions result. diff --git a/python/semantic_kernel/template_engine/protocols/code_renderer.py b/python/semantic_kernel/template_engine/protocols/code_renderer.py index 5ff77a69f0da..549bc7de3618 100644 --- a/python/semantic_kernel/template_engine/protocols/code_renderer.py +++ b/python/semantic_kernel/template_engine/protocols/code_renderer.py @@ -2,7 +2,7 @@ from typing import Protocol, runtime_checkable -from semantic_kernel.orchestration.sk_context import SKContext +from semantic_kernel.orchestration.kernel_context import KernelContext @runtime_checkable @@ -11,11 +11,11 @@ class CodeRenderer(Protocol): Protocol for dynamic code blocks that need async IO to be rendered. """ - async def render_code_async(self, context: SKContext) -> str: + async def render_code_async(self, context: KernelContext) -> str: """ Render the block using the given context. - :param context: SK execution context + :param context: kernel execution context :return: Rendered content """ ... diff --git a/python/semantic_kernel/template_engine/protocols/prompt_templating_engine.py b/python/semantic_kernel/template_engine/protocols/prompt_templating_engine.py index 31556cc8768e..c13f515c3677 100644 --- a/python/semantic_kernel/template_engine/protocols/prompt_templating_engine.py +++ b/python/semantic_kernel/template_engine/protocols/prompt_templating_engine.py @@ -6,7 +6,7 @@ from semantic_kernel.template_engine.blocks.block import Block if TYPE_CHECKING: - from semantic_kernel.orchestration.sk_context import SKContext + from semantic_kernel.orchestration.kernel_context import KernelContext @runtime_checkable @@ -28,7 +28,7 @@ def extract_blocks(self, template_text: Optional[str] = None, validate: bool = T """ ... - async def render_async(self, template_text: str, context: "SKContext") -> str: + async def render_async(self, template_text: str, context: "KernelContext") -> str: """ Given a prompt template, replace the variables with their values and execute the functions replacing their reference with the @@ -40,7 +40,7 @@ async def render_async(self, template_text: str, context: "SKContext") -> str: """ ... - async def render_blocks_async(self, blocks: List[Block], context: "SKContext") -> str: + async def render_blocks_async(self, blocks: List[Block], context: "KernelContext") -> str: """ Given a list of blocks render each block and compose the final result. @@ -62,7 +62,7 @@ def render_variables(self, blocks: List[Block], variables: Optional[ContextVaria """ ... - async def render_code_async(self, blocks: List[Block], execution_context: "SKContext") -> List[Block]: + async def render_code_async(self, blocks: List[Block], execution_context: "KernelContext") -> List[Block]: """ Given a list of blocks, render the Code Blocks, executing the functions and replacing placeholders with the functions result. diff --git a/python/semantic_kernel/template_engine/template_tokenizer.py b/python/semantic_kernel/template_engine/template_tokenizer.py index 1f5446f1e101..f4e4f2b5b57d 100644 --- a/python/semantic_kernel/template_engine/template_tokenizer.py +++ b/python/semantic_kernel/template_engine/template_tokenizer.py @@ -5,7 +5,7 @@ from pydantic import PrivateAttr -from semantic_kernel.sk_pydantic import SKBaseModel +from semantic_kernel.kernel_pydantic import KernelBaseModel from semantic_kernel.template_engine.blocks.block import Block from semantic_kernel.template_engine.blocks.block_types import BlockTypes from semantic_kernel.template_engine.blocks.code_block import CodeBlock @@ -24,7 +24,7 @@ # | "{{" [function-call] "}}" # [text-block] ::= [any-char] | [any-char] [text-block] # [any-char] ::= any char -class TemplateTokenizer(SKBaseModel): +class TemplateTokenizer(KernelBaseModel): _code_tokenizer: CodeTokenizer = PrivateAttr() def __init__(self, log: Optional[Any] = None): diff --git a/python/semantic_kernel/text/function_extension.py b/python/semantic_kernel/text/function_extension.py index bd5c2825e87e..94051feb23ec 100644 --- a/python/semantic_kernel/text/function_extension.py +++ b/python/semantic_kernel/text/function_extension.py @@ -2,13 +2,13 @@ from typing import List -from semantic_kernel.orchestration.sk_context import SKContext -from semantic_kernel.orchestration.sk_function import SKFunction +from semantic_kernel.orchestration.kernel_context import KernelContext +from semantic_kernel.orchestration.kernel_function import KernelFunction async def aggregate_chunked_results_async( - func: SKFunction, chunked_results: List[str], context: SKContext -) -> SKContext: + func: KernelFunction, chunked_results: List[str], context: KernelContext +) -> KernelContext: """ Aggregate the results from the chunked results. """ diff --git a/python/tests/conftest.py b/python/tests/conftest.py index b952a75b9330..ea4a6f19c9bb 100644 --- a/python/tests/conftest.py +++ b/python/tests/conftest.py @@ -9,8 +9,8 @@ import semantic_kernel as sk from semantic_kernel.memory.null_memory import NullMemory from semantic_kernel.orchestration.context_variables import ContextVariables -from semantic_kernel.orchestration.sk_context import SKContext -from semantic_kernel.orchestration.sk_function import SKFunction +from semantic_kernel.orchestration.kernel_context import KernelContext +from semantic_kernel.orchestration.kernel_function import KernelFunction from semantic_kernel.plugin_definition.read_only_plugin_collection import ( ReadOnlyPluginCollection, ) @@ -87,12 +87,12 @@ def get_oai_config(): @pytest.fixture() -def context_factory() -> t.Callable[[ContextVariables], SKContext]: - """Return a factory for SKContext objects.""" +def context_factory() -> t.Callable[[ContextVariables], KernelContext]: + """Return a factory for KernelContext objects.""" - def create_context(context_variables: ContextVariables, *functions: SKFunction) -> SKContext: - """Return a SKContext object.""" - return SKContext( + def create_context(context_variables: ContextVariables, *functions: KernelFunction) -> KernelContext: + """Return a KernelContext object.""" + return KernelContext( context_variables, NullMemory(), plugin_collection=ReadOnlyPluginCollection( diff --git a/python/tests/integration/fakes/email_plugin_fake.py b/python/tests/integration/fakes/email_plugin_fake.py index cbdec90ec1d1..0ad4d0bd0d63 100644 --- a/python/tests/integration/fakes/email_plugin_fake.py +++ b/python/tests/integration/fakes/email_plugin_fake.py @@ -1,17 +1,17 @@ # Copyright (c) Microsoft. All rights reserved. -from semantic_kernel.plugin_definition.sk_function_decorator import sk_function +from semantic_kernel.plugin_definition.kernel_function_decorator import kernel_function class EmailPluginFake: - @sk_function( + @kernel_function( description="Given an email address and message body, send an email", name="SendEmail", ) def send_email(self, input: str) -> str: return f"Sent email to: . Body: {input}" - @sk_function( + @kernel_function( description="Lookup an email address for a person given a name", name="GetEmailAddress", ) @@ -20,6 +20,6 @@ def get_email_address(self, input: str) -> str: return "johndoe1234@example.com" return f"{input}@example.com" - @sk_function(description="Write a short poem for an e-mail", name="WritePoem") + @kernel_function(description="Write a short poem for an e-mail", name="WritePoem") def write_poem(self, input: str) -> str: return f"Roses are red, violets are blue, {input} is hard, so is this test." diff --git a/python/tests/integration/fakes/fun_plugin_fake.py b/python/tests/integration/fakes/fun_plugin_fake.py index 30c8d279eeb2..215861c7e0da 100644 --- a/python/tests/integration/fakes/fun_plugin_fake.py +++ b/python/tests/integration/fakes/fun_plugin_fake.py @@ -1,13 +1,13 @@ # Copyright (c) Microsoft. All rights reserved. -from semantic_kernel.plugin_definition.sk_function_decorator import sk_function +from semantic_kernel.plugin_definition.kernel_function_decorator import kernel_function # TODO: this fake plugin is temporal usage. # C# supports import plugin from samples dir by using test helper and python should do the same # `semantic-kernel/dotnet/src/IntegrationTests/TestHelpers.cs` class FunPluginFake: - @sk_function( + @kernel_function( description="Write a joke", name="WriteJoke", ) diff --git a/python/tests/integration/fakes/summarize_plugin_fake.py b/python/tests/integration/fakes/summarize_plugin_fake.py index 912b223fb272..8d20c88c4618 100644 --- a/python/tests/integration/fakes/summarize_plugin_fake.py +++ b/python/tests/integration/fakes/summarize_plugin_fake.py @@ -1,6 +1,6 @@ # Copyright (c) Microsoft. All rights reserved. -from semantic_kernel.plugin_definition.sk_function_decorator import sk_function +from semantic_kernel.plugin_definition.kernel_function_decorator import kernel_function # TODO: this fake plugin is temporal usage. # C# supports import plugin from samples dir by using test helper and python should do the same @@ -8,7 +8,7 @@ class SummarizePluginFake: - @sk_function( + @kernel_function( description="Summarize", name="Summarize", ) diff --git a/python/tests/integration/fakes/writer_plugin_fake.py b/python/tests/integration/fakes/writer_plugin_fake.py index 3474e37ed589..548e1859db35 100644 --- a/python/tests/integration/fakes/writer_plugin_fake.py +++ b/python/tests/integration/fakes/writer_plugin_fake.py @@ -1,6 +1,6 @@ # Copyright (c) Microsoft. All rights reserved. -from semantic_kernel.plugin_definition import sk_function, sk_function_context_parameter +from semantic_kernel.plugin_definition import kernel_function, kernel_function_context_parameter # TODO: this fake plugin is temporal usage. # C# supports import plugin from samples dir by using test helper and python should do the same @@ -8,15 +8,15 @@ class WriterPluginFake: - @sk_function( + @kernel_function( description="Translate", name="Translate", ) def translate(self, language: str) -> str: return f"Translate: {language}" - @sk_function(description="Write an outline for a novel", name="NovelOutline") - @sk_function_context_parameter( + @kernel_function(description="Write an outline for a novel", name="NovelOutline") + @kernel_function_context_parameter( name="endMarker", description="The marker to use to end each chapter.", default_value="", diff --git a/python/tests/integration/planning/stepwise_planner/test_stepwise_planner.py b/python/tests/integration/planning/stepwise_planner/test_stepwise_planner.py index 08a6f33d9a1b..a48f9bb693e8 100644 --- a/python/tests/integration/planning/stepwise_planner/test_stepwise_planner.py +++ b/python/tests/integration/planning/stepwise_planner/test_stepwise_planner.py @@ -11,12 +11,12 @@ from semantic_kernel.core_plugins.math_plugin import MathPlugin from semantic_kernel.core_plugins.time_plugin import TimePlugin from semantic_kernel.kernel import Kernel -from semantic_kernel.orchestration.sk_context import SKContext +from semantic_kernel.orchestration.kernel_context import KernelContext from semantic_kernel.planning import StepwisePlanner from semantic_kernel.planning.stepwise_planner.stepwise_planner_config import ( StepwisePlannerConfig, ) -from semantic_kernel.plugin_definition import sk_function, sk_function_context_parameter +from semantic_kernel.plugin_definition import kernel_function, kernel_function_context_parameter class TempWebSearchEnginePlugin: @@ -35,12 +35,12 @@ class TempWebSearchEnginePlugin: def __init__(self, connector) -> None: self._connector = connector - @sk_function(description="Performs a web search for a given query", name="searchAsync") - @sk_function_context_parameter( + @kernel_function(description="Performs a web search for a given query", name="searchAsync") + @kernel_function_context_parameter( name="query", description="The search query", ) - async def search_async(self, query: str, context: SKContext) -> str: + async def search_async(self, query: str, context: KernelContext) -> str: query = query or context.variables.get("query") result = await self._connector.search_async(query, num_results=5, offset=0) return str(result) diff --git a/python/tests/template_engine/prompt_template_e2e_tests.py b/python/tests/template_engine/prompt_template_e2e_tests.py index 27bd8272778c..4f4821d22e0e 100644 --- a/python/tests/template_engine/prompt_template_e2e_tests.py +++ b/python/tests/template_engine/prompt_template_e2e_tests.py @@ -6,7 +6,7 @@ from pytest import mark, raises from semantic_kernel import Kernel -from semantic_kernel.plugin_definition import sk_function +from semantic_kernel.plugin_definition import kernel_function from semantic_kernel.template_engine.prompt_template_engine import PromptTemplateEngine @@ -34,11 +34,11 @@ def _get_template_language_tests() -> List[Tuple[str, str]]: class MyPlugin: - @sk_function() + @kernel_function() def check123(self, input: str) -> str: return "123 ok" if input == "123" else f"{input} != 123" - @sk_function() + @kernel_function() def asis(self, input: str) -> str: return input diff --git a/python/tests/test_native_plugins/TestNativePlugin/native_function.py b/python/tests/test_native_plugins/TestNativePlugin/native_function.py index 4808ba87dca9..0510b52005a6 100644 --- a/python/tests/test_native_plugins/TestNativePlugin/native_function.py +++ b/python/tests/test_native_plugins/TestNativePlugin/native_function.py @@ -1,4 +1,4 @@ -from semantic_kernel.plugin_definition import sk_function +from semantic_kernel.plugin_definition import kernel_function class TestNativeEchoBotPlugin: @@ -6,7 +6,7 @@ class TestNativeEchoBotPlugin: Description: Test Native Plugin for testing purposes """ - @sk_function( + @kernel_function( description="Echo for input text", name="echoAsync", input_description="The text to echo", diff --git a/python/tests/unit/ai/open_ai/services/test_azure_chat_completion.py b/python/tests/unit/ai/open_ai/services/test_azure_chat_completion.py index 44a3cd53c4d9..ad8ba00456ee 100644 --- a/python/tests/unit/ai/open_ai/services/test_azure_chat_completion.py +++ b/python/tests/unit/ai/open_ai/services/test_azure_chat_completion.py @@ -514,3 +514,50 @@ async def test_azure_chat_completion_content_filtering_raises_correct_exception( assert content_filter_exc.content_filter_code == ContentFilterCodes.RESPONSIBLE_AI_POLICY_VIOLATION assert content_filter_exc.content_filter_result["hate"].filtered assert content_filter_exc.content_filter_result["hate"].severity == ContentFilterResultSeverity.HIGH + + +@pytest.mark.asyncio +@patch.object(AsyncChatCompletions, "create") +async def test_azure_chat_completion_content_filtering_without_response_code_raises_with_default_code( + mock_create, +) -> None: + deployment_name = "test_deployment" + endpoint = "https://test-endpoint.com" + api_key = "test_api_key" + api_version = "2023-03-15-preview" + prompt = "some prompt that would trigger the content filtering" + messages = [{"role": "user", "content": prompt}] + complete_request_settings = AzureChatRequestSettings() + + mock_create.side_effect = openai.BadRequestError( + CONTENT_FILTERED_ERROR_FULL_MESSAGE, + response=Response(400, request=Request("POST", endpoint)), + body={ + "message": CONTENT_FILTERED_ERROR_MESSAGE, + "type": None, + "param": "prompt", + "code": "content_filter", + "status": 400, + "innererror": { + "content_filter_result": { + "hate": {"filtered": True, "severity": "high"}, + "self_harm": {"filtered": False, "severity": "safe"}, + "sexual": {"filtered": False, "severity": "safe"}, + "violence": {"filtered": False, "severity": "safe"}, + }, + }, + }, + ) + + azure_chat_completion = AzureChatCompletion( + deployment_name=deployment_name, + endpoint=endpoint, + api_key=api_key, + api_version=api_version, + ) + + with pytest.raises(ContentFilterAIException, match="service encountered a content error") as exc_info: + await azure_chat_completion.complete_chat_async(messages, complete_request_settings) + + content_filter_exc = exc_info.value + assert content_filter_exc.content_filter_code == ContentFilterCodes.RESPONSIBLE_AI_POLICY_VIOLATION diff --git a/python/tests/unit/kernel_extensions/test_register_functions.py b/python/tests/unit/kernel_extensions/test_register_functions.py index 35e12384364c..41fc4ce62391 100644 --- a/python/tests/unit/kernel_extensions/test_register_functions.py +++ b/python/tests/unit/kernel_extensions/test_register_functions.py @@ -5,16 +5,16 @@ from semantic_kernel import Kernel from semantic_kernel.kernel_exception import KernelException -from semantic_kernel.orchestration.sk_function_base import SKFunctionBase +from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase +from semantic_kernel.plugin_definition.kernel_function_decorator import kernel_function from semantic_kernel.plugin_definition.plugin_collection import PluginCollection -from semantic_kernel.plugin_definition.sk_function_decorator import sk_function def not_decorated_native_function(arg1: str) -> str: return "test" -@sk_function(name="getLightStatus") +@kernel_function(name="getLightStatus") def decorated_native_function(arg1: str) -> str: return "test" @@ -24,7 +24,7 @@ def test_register_valid_native_function(): registered_func = kernel.register_native_function("TestPlugin", decorated_native_function) - assert isinstance(registered_func, SKFunctionBase) + assert isinstance(registered_func, KernelFunctionBase) assert kernel.plugins.get_native_function("TestPlugin", "getLightStatus") == registered_func assert registered_func.invoke("testtest").result == "test" diff --git a/python/tests/unit/openapi/test_sk_openapi.py b/python/tests/unit/openapi/test_sk_openapi.py index 07539da92883..b412ea42987f 100644 --- a/python/tests/unit/openapi/test_sk_openapi.py +++ b/python/tests/unit/openapi/test_sk_openapi.py @@ -8,7 +8,7 @@ from semantic_kernel.connectors.ai.open_ai.const import ( USER_AGENT, ) -from semantic_kernel.connectors.openapi.sk_openapi import ( +from semantic_kernel.connectors.openapi.kernel_openapi import ( OpenApiParser, OpenApiRunner, PreparedRestApiRequest, diff --git a/python/tests/unit/orchestration/test_native_function.py b/python/tests/unit/orchestration/test_native_function.py index 88b6b1f81e47..08bb653e9d36 100644 --- a/python/tests/unit/orchestration/test_native_function.py +++ b/python/tests/unit/orchestration/test_native_function.py @@ -2,23 +2,23 @@ from typing import TYPE_CHECKING -from semantic_kernel.orchestration.sk_function import SKFunction -from semantic_kernel.plugin_definition.sk_function_decorator import sk_function +from semantic_kernel.orchestration.kernel_function import KernelFunction +from semantic_kernel.plugin_definition.kernel_function_decorator import kernel_function if TYPE_CHECKING: - from semantic_kernel.orchestration.sk_context import SKContext + from semantic_kernel.orchestration.kernel_context import KernelContext def test_init_native_function_with_input_description(): - def mock_function(input: str, context: "SKContext") -> None: + def mock_function(input: str, context: "KernelContext") -> None: pass - mock_function.__sk_function__ = True - mock_function.__sk_function_name__ = "mock_function" - mock_function.__sk_function_description__ = "Mock description" - mock_function.__sk_function_input_description__ = "Mock input description" - mock_function.__sk_function_input_default_value__ = "default_input_value" - mock_function.__sk_function_context_parameters__ = [ + mock_function.__kernel_function__ = True + mock_function.__kernel_function_name__ = "mock_function" + mock_function.__kernel_function_description__ = "Mock description" + mock_function.__kernel_function_input_description__ = "Mock input description" + mock_function.__kernel_function_input_default_value__ = "default_input_value" + mock_function.__kernel_function_context_parameters__ = [ { "name": "param1", "description": "Param 1 description", @@ -28,7 +28,7 @@ def mock_function(input: str, context: "SKContext") -> None: mock_method = mock_function - native_function = SKFunction.from_native_method(mock_method, "MockPlugin") + native_function = KernelFunction.from_native_method(mock_method, "MockPlugin") assert native_function._function == mock_method assert native_function._parameters[0].name == "input" @@ -44,13 +44,13 @@ def mock_function(input: str, context: "SKContext") -> None: def test_init_native_function_without_input_description(): - def mock_function(context: "SKContext") -> None: + def mock_function(context: "KernelContext") -> None: pass - mock_function.__sk_function__ = True - mock_function.__sk_function_name__ = "mock_function_no_input_desc" - mock_function.__sk_function_description__ = "Mock description no input desc" - mock_function.__sk_function_context_parameters__ = [ + mock_function.__kernel_function__ = True + mock_function.__kernel_function_name__ = "mock_function_no_input_desc" + mock_function.__kernel_function_description__ = "Mock description no input desc" + mock_function.__kernel_function_context_parameters__ = [ { "name": "param1", "description": "Param 1 description", @@ -61,7 +61,7 @@ def mock_function(context: "SKContext") -> None: mock_method = mock_function - native_function = SKFunction.from_native_method(mock_method, "MockPlugin") + native_function = KernelFunction.from_native_method(mock_method, "MockPlugin") assert native_function._function == mock_method assert native_function._parameters[0].name == "param1" @@ -71,8 +71,8 @@ def mock_function(context: "SKContext") -> None: assert native_function._parameters[0].required is True -def test_init_native_function_from_sk_function_decorator(): - @sk_function( +def test_init_native_function_from_kernel_function_decorator(): + @kernel_function( description="Test description", name="test_function", input_description="Test input description", @@ -81,13 +81,13 @@ def test_init_native_function_from_sk_function_decorator(): def decorated_function() -> None: pass - assert decorated_function.__sk_function__ is True - assert decorated_function.__sk_function_description__ == "Test description" - assert decorated_function.__sk_function_name__ == "test_function" - assert decorated_function.__sk_function_input_description__ == "Test input description" - assert decorated_function.__sk_function_input_default_value__ == "test_default_value" + assert decorated_function.__kernel_function__ is True + assert decorated_function.__kernel_function_description__ == "Test description" + assert decorated_function.__kernel_function_name__ == "test_function" + assert decorated_function.__kernel_function_input_description__ == "Test input description" + assert decorated_function.__kernel_function_input_default_value__ == "test_default_value" - native_function = SKFunction.from_native_method(decorated_function, "MockPlugin") + native_function = KernelFunction.from_native_method(decorated_function, "MockPlugin") assert native_function._function == decorated_function assert native_function._parameters[0].name == "input" @@ -97,18 +97,18 @@ def decorated_function() -> None: assert native_function._parameters[0].required is False -def test_init_native_function_from_sk_function_decorator_defaults(): - @sk_function() +def test_init_native_function_from_kernel_function_decorator_defaults(): + @kernel_function() def decorated_function() -> None: pass - assert decorated_function.__sk_function__ is True - assert decorated_function.__sk_function_description__ == "" - assert decorated_function.__sk_function_name__ == "decorated_function" - assert decorated_function.__sk_function_input_description__ == "" - assert decorated_function.__sk_function_input_default_value__ == "" + assert decorated_function.__kernel_function__ is True + assert decorated_function.__kernel_function_description__ == "" + assert decorated_function.__kernel_function_name__ == "decorated_function" + assert decorated_function.__kernel_function_input_description__ == "" + assert decorated_function.__kernel_function_input_default_value__ == "" - native_function = SKFunction.from_native_method(decorated_function, "MockPlugin") + native_function = KernelFunction.from_native_method(decorated_function, "MockPlugin") assert native_function._function == decorated_function assert len(native_function._parameters) == 0 diff --git a/python/tests/unit/planning/action_planner/test_action_planner.py b/python/tests/unit/planning/action_planner/test_action_planner.py index e67c06f91762..b292c07947d4 100644 --- a/python/tests/unit/planning/action_planner/test_action_planner.py +++ b/python/tests/unit/planning/action_planner/test_action_planner.py @@ -6,8 +6,8 @@ from semantic_kernel import Kernel from semantic_kernel.memory.semantic_text_memory import SemanticTextMemoryBase from semantic_kernel.orchestration.context_variables import ContextVariables -from semantic_kernel.orchestration.sk_context import SKContext -from semantic_kernel.orchestration.sk_function_base import SKFunctionBase +from semantic_kernel.orchestration.kernel_context import KernelContext +from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase from semantic_kernel.planning import ActionPlanner from semantic_kernel.planning.action_planner.action_planner_config import ( ActionPlannerConfig, @@ -20,8 +20,8 @@ ) -def create_mock_function(function_view: FunctionView) -> Mock(spec=SKFunctionBase): - mock_function = Mock(spec=SKFunctionBase) +def create_mock_function(function_view: FunctionView) -> Mock(spec=KernelFunctionBase): + mock_function = Mock(spec=KernelFunctionBase) mock_function.describe.return_value = function_view mock_function.name = function_view.name mock_function.plugin_name = function_view.plugin_name @@ -54,7 +54,7 @@ async def test_plan_creation_async(): ) kernel = Mock(spec=Kernel) - mock_function = Mock(spec=SKFunctionBase) + mock_function = Mock(spec=KernelFunctionBase) memory = Mock(spec=SemanticTextMemoryBase) plugins = Mock(spec=PluginCollectionBase) @@ -68,8 +68,10 @@ async def test_plan_creation_async(): mock_function = create_mock_function(function_view) plugins.get_function.return_value = mock_function - context = SKContext.model_construct(variables=ContextVariables(), memory=memory, plugin_collection=plugins) - return_context = SKContext.model_construct(variables=ContextVariables(), memory=memory, plugin_collection=plugins) + context = KernelContext.model_construct(variables=ContextVariables(), memory=memory, plugin_collection=plugins) + return_context = KernelContext.model_construct( + variables=ContextVariables(), memory=memory, plugin_collection=plugins + ) return_context.variables.update(plan_str) @@ -101,7 +103,7 @@ def plugins_input(): @pytest.fixture def mock_context(plugins_input): memory = Mock(spec=Kernel) - context = Mock(spec=SKContext) + context = Mock(spec=KernelContext) functionsView = FunctionsView() plugins = Mock(spec=PluginCollectionBase) @@ -111,7 +113,7 @@ def mock_context(plugins_input): mock_function = create_mock_function(function_view) functionsView.add_function(function_view) - _context = SKContext.model_construct(variables=ContextVariables(), memory=memory, plugin_collection=plugins) + _context = KernelContext.model_construct(variables=ContextVariables(), memory=memory, plugin_collection=plugins) _context.variables.update("MOCK FUNCTION CALLED") mock_function.invoke_async.return_value = _context mock_functions.append(mock_function) @@ -182,7 +184,7 @@ async def test_invalid_json_throw_async(): plan_str = '{"":{""function"": ""WriterPlugin.Translate""}}' kernel = Mock(spec=Kernel) - mock_function = Mock(spec=SKFunctionBase) + mock_function = Mock(spec=KernelFunctionBase) memory = Mock(spec=SemanticTextMemoryBase) plugins = Mock(spec=PluginCollectionBase) @@ -196,8 +198,10 @@ async def test_invalid_json_throw_async(): mock_function = create_mock_function(function_view) plugins.get_function.return_value = mock_function - context = SKContext.model_construct(variables=ContextVariables(), memory=memory, plugin_collection=plugins) - return_context = SKContext.model_construct(variables=ContextVariables(), memory=memory, plugin_collection=plugins) + context = KernelContext.model_construct(variables=ContextVariables(), memory=memory, plugin_collection=plugins) + return_context = KernelContext.model_construct( + variables=ContextVariables(), memory=memory, plugin_collection=plugins + ) return_context.variables.update(plan_str) @@ -217,7 +221,7 @@ async def test_empty_goal_throw_async(): goal = "" kernel = Mock(spec=Kernel) - mock_function = Mock(spec=SKFunctionBase) + mock_function = Mock(spec=KernelFunctionBase) memory = Mock(spec=SemanticTextMemoryBase) plugins = Mock(spec=PluginCollectionBase) @@ -231,8 +235,10 @@ async def test_empty_goal_throw_async(): mock_function = create_mock_function(function_view) plugins.get_function.return_value = mock_function - context = SKContext.model_construct(variables=ContextVariables(), memory=memory, plugin_collection=plugins) - return_context = SKContext.model_construct(variables=ContextVariables(), memory=memory, plugin_collection=plugins) + context = KernelContext.model_construct(variables=ContextVariables(), memory=memory, plugin_collection=plugins) + return_context = KernelContext.model_construct( + variables=ContextVariables(), memory=memory, plugin_collection=plugins + ) mock_function.invoke_async.return_value = return_context kernel.create_semantic_function.return_value = mock_function diff --git a/python/tests/unit/planning/sequential_planner/test_sequential_planner.py b/python/tests/unit/planning/sequential_planner/test_sequential_planner.py index 58f35dd0ba0b..acd353cbb2c1 100644 --- a/python/tests/unit/planning/sequential_planner/test_sequential_planner.py +++ b/python/tests/unit/planning/sequential_planner/test_sequential_planner.py @@ -7,8 +7,8 @@ from semantic_kernel.kernel import Kernel from semantic_kernel.memory.semantic_text_memory import SemanticTextMemoryBase from semantic_kernel.orchestration.context_variables import ContextVariables -from semantic_kernel.orchestration.sk_context import SKContext -from semantic_kernel.orchestration.sk_function_base import SKFunctionBase +from semantic_kernel.orchestration.kernel_context import KernelContext +from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase from semantic_kernel.planning.planning_exception import PlanningException from semantic_kernel.planning.sequential_planner.sequential_planner import ( SequentialPlanner, @@ -21,7 +21,7 @@ def create_mock_function(function_view: FunctionView): - mock_function = Mock(spec=SKFunctionBase) + mock_function = Mock(spec=KernelFunctionBase) mock_function.describe.return_value = function_view mock_function.name = function_view.name mock_function.plugin_name = function_view.plugin_name @@ -51,7 +51,7 @@ async def test_it_can_create_plan_async(goal): mock_function = create_mock_function(function_view) functionsView.add_function(function_view) - context = SKContext.model_construct(variables=ContextVariables(), memory=memory, plugin_collection=plugins) + context = KernelContext.model_construct(variables=ContextVariables(), memory=memory, plugin_collection=plugins) context.variables.update("MOCK FUNCTION CALLED") mock_function.invoke_async.return_value = context mock_functions.append(mock_function) @@ -65,8 +65,10 @@ async def test_it_can_create_plan_async(goal): expected_functions = [x[0] for x in input] expected_plugins = [x[1] for x in input] - context = SKContext.model_construct(variables=ContextVariables(), memory=memory, plugin_collection=plugins) - return_context = SKContext.model_construct(variables=ContextVariables(), memory=memory, plugin_collection=plugins) + context = KernelContext.model_construct(variables=ContextVariables(), memory=memory, plugin_collection=plugins) + return_context = KernelContext.model_construct( + variables=ContextVariables(), memory=memory, plugin_collection=plugins + ) plan_string = """ @@ -77,7 +79,7 @@ async def test_it_can_create_plan_async(goal): return_context.variables.update(plan_string) - mock_function_flow_function = Mock(spec=SKFunctionBase) + mock_function_flow_function = Mock(spec=KernelFunctionBase) mock_function_flow_function.invoke_async.return_value = return_context kernel.plugins = plugins @@ -120,15 +122,15 @@ async def test_invalid_xml_throws_async(): plugins.get_functions_view.return_value = functionsView plan_string = "notvalid<" - return_context = SKContext.model_construct( + return_context = KernelContext.model_construct( variables=ContextVariables(plan_string), memory=memory, plugin_collection=plugins, ) - context = SKContext.model_construct(variables=ContextVariables(), memory=memory, plugin_collection=plugins) + context = KernelContext.model_construct(variables=ContextVariables(), memory=memory, plugin_collection=plugins) - mock_function_flow_function = Mock(spec=SKFunctionBase) + mock_function_flow_function = Mock(spec=KernelFunctionBase) mock_function_flow_function.invoke_async.return_value = return_context kernel.plugins = plugins diff --git a/python/tests/unit/planning/sequential_planner/test_sequential_planner_extensions.py b/python/tests/unit/planning/sequential_planner/test_sequential_planner_extensions.py index fcced3d7accc..d16874269d8c 100644 --- a/python/tests/unit/planning/sequential_planner/test_sequential_planner_extensions.py +++ b/python/tests/unit/planning/sequential_planner/test_sequential_planner_extensions.py @@ -7,14 +7,14 @@ from semantic_kernel.memory.memory_query_result import MemoryQueryResult from semantic_kernel.memory.semantic_text_memory_base import SemanticTextMemoryBase from semantic_kernel.orchestration.context_variables import ContextVariables -from semantic_kernel.orchestration.sk_context import SKContext -from semantic_kernel.orchestration.sk_function_base import SKFunctionBase +from semantic_kernel.orchestration.kernel_context import KernelContext +from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase from semantic_kernel.planning.sequential_planner.sequential_planner_config import ( SequentialPlannerConfig, ) from semantic_kernel.planning.sequential_planner.sequential_planner_extensions import ( SequentialPlannerFunctionViewExtension, - SequentialPlannerSKContextExtension, + SequentialPlannerKernelContextExtension, ) from semantic_kernel.plugin_definition.function_view import FunctionView from semantic_kernel.plugin_definition.functions_view import FunctionsView @@ -49,12 +49,14 @@ async def test_can_call_get_available_functions_with_no_functions_async(): memory.search_async.return_value = async_enumerable # Arrange GetAvailableFunctionsAsync parameters - context = SKContext(variables, memory, plugins.read_only_plugin_collection) + context = KernelContext(variables, memory, plugins.read_only_plugin_collection) config = SequentialPlannerConfig() semantic_query = "test" # Act - result = await SequentialPlannerSKContextExtension.get_available_functions_async(context, config, semantic_query) + result = await SequentialPlannerKernelContextExtension.get_available_functions_async( + context, config, semantic_query + ) # Assert assert result is not None @@ -65,7 +67,7 @@ async def test_can_call_get_available_functions_with_no_functions_async(): async def test_can_call_get_available_functions_with_functions_async(): variables = ContextVariables() - function_mock = Mock(spec=SKFunctionBase) + function_mock = Mock(spec=KernelFunctionBase) functions_view = FunctionsView() function_view = FunctionView( "functionName", @@ -106,12 +108,14 @@ async def test_can_call_get_available_functions_with_functions_async(): memory.search_async.return_value = async_enumerable # Arrange GetAvailableFunctionsAsync parameters - context = SKContext.model_construct(variables=variables, memory=memory, plugin_collection=plugins) + context = KernelContext.model_construct(variables=variables, memory=memory, plugin_collection=plugins) config = SequentialPlannerConfig() semantic_query = "test" # Act - result = await SequentialPlannerSKContextExtension.get_available_functions_async(context, config, semantic_query) + result = await SequentialPlannerKernelContextExtension.get_available_functions_async( + context, config, semantic_query + ) # Assert assert result is not None @@ -122,7 +126,9 @@ async def test_can_call_get_available_functions_with_functions_async(): config.included_functions.append(["nativeFunctionName"]) # Act - result = await SequentialPlannerSKContextExtension.get_available_functions_async(context, config, semantic_query) + result = await SequentialPlannerKernelContextExtension.get_available_functions_async( + context, config, semantic_query + ) # Assert assert result is not None @@ -137,7 +143,7 @@ async def test_can_call_get_available_functions_with_functions_and_relevancy_asy variables = ContextVariables() # Arrange FunctionView - function_mock = Mock(spec=SKFunctionBase) + function_mock = Mock(spec=KernelFunctionBase) functions_view = FunctionsView() function_view = FunctionView( "functionName", @@ -178,7 +184,7 @@ async def test_can_call_get_available_functions_with_functions_and_relevancy_asy plugins.read_only_plugin_collection = plugins # Arrange GetAvailableFunctionsAsync parameters - context = SKContext.model_construct( + context = KernelContext.model_construct( variables=variables, memory=memory, plugin_collection=plugins, @@ -187,7 +193,9 @@ async def test_can_call_get_available_functions_with_functions_and_relevancy_asy semantic_query = "test" # Act - result = await SequentialPlannerSKContextExtension.get_available_functions_async(context, config, semantic_query) + result = await SequentialPlannerKernelContextExtension.get_available_functions_async( + context, config, semantic_query + ) # Assert assert result is not None @@ -199,7 +207,9 @@ async def test_can_call_get_available_functions_with_functions_and_relevancy_asy memory.search_async.return_value = _async_generator(memory_query_result) # Act - result = await SequentialPlannerSKContextExtension.get_available_functions_async(context, config, semantic_query) + result = await SequentialPlannerKernelContextExtension.get_available_functions_async( + context, config, semantic_query + ) # Assert assert result is not None @@ -230,12 +240,14 @@ async def test_can_call_get_available_functions_async_with_default_relevancy_asy memory.search_async.return_value = async_enumerable # Arrange GetAvailableFunctionsAsync parameters - context = SKContext.model_construct(variables=variables, memory=memory, plugin_collection=plugins) + context = KernelContext.model_construct(variables=variables, memory=memory, plugin_collection=plugins) config = SequentialPlannerConfig(relevancy_threshold=0.78) semantic_query = "test" # Act - result = await SequentialPlannerSKContextExtension.get_available_functions_async(context, config, semantic_query) + result = await SequentialPlannerKernelContextExtension.get_available_functions_async( + context, config, semantic_query + ) # Assert assert result is not None diff --git a/python/tests/unit/planning/sequential_planner/test_sequential_planner_parser.py b/python/tests/unit/planning/sequential_planner/test_sequential_planner_parser.py index 28479d0e54d2..085b0c7123ab 100644 --- a/python/tests/unit/planning/sequential_planner/test_sequential_planner_parser.py +++ b/python/tests/unit/planning/sequential_planner/test_sequential_planner_parser.py @@ -5,7 +5,7 @@ import pytest from semantic_kernel.kernel import Kernel -from semantic_kernel.orchestration.sk_function_base import SKFunctionBase +from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase from semantic_kernel.planning.planning_exception import PlanningException from semantic_kernel.planning.sequential_planner.sequential_planner_parser import ( SequentialPlanParser, @@ -14,8 +14,8 @@ from semantic_kernel.plugin_definition.functions_view import FunctionsView -def create_mock_function(function_view: FunctionView) -> SKFunctionBase: - mock_function = Mock(spec=SKFunctionBase) +def create_mock_function(function_view: FunctionView) -> KernelFunctionBase: + mock_function = Mock(spec=KernelFunctionBase) mock_function.describe.return_value = function_view mock_function.name = function_view.name mock_function.plugin_name = function_view.plugin_name diff --git a/python/tests/unit/plugin_definition/test_sk_function_decorators.py b/python/tests/unit/plugin_definition/test_kernel_function_decorators.py similarity index 50% rename from python/tests/unit/plugin_definition/test_sk_function_decorators.py rename to python/tests/unit/plugin_definition/test_kernel_function_decorators.py index 0e39d36aafc4..f2fda443b5f4 100644 --- a/python/tests/unit/plugin_definition/test_sk_function_decorators.py +++ b/python/tests/unit/plugin_definition/test_kernel_function_decorators.py @@ -1,18 +1,18 @@ -from semantic_kernel.plugin_definition import sk_function +from semantic_kernel.plugin_definition import kernel_function class MiscClass: __test__ = False - @sk_function(description="description") + @kernel_function(description="description") def func_with_description(self, input): return input - @sk_function(description="description") + @kernel_function(description="description") def func_no_name(self, input): return input - @sk_function(description="description", name="my-name") + @kernel_function(description="description", name="my-name") def func_with_name(self, input): return input @@ -20,16 +20,16 @@ def func_with_name(self, input): def test_description(): decorator_test = MiscClass() my_func = getattr(decorator_test, "func_with_description") - assert my_func.__sk_function_description__ == "description" + assert my_func.__kernel_function_description__ == "description" -def test_sk_function_name_not_specified(): +def test_kernel_function_name_not_specified(): decorator_test = MiscClass() my_func = getattr(decorator_test, "func_no_name") - assert my_func.__sk_function_name__ == "func_no_name" + assert my_func.__kernel_function_name__ == "func_no_name" -def test_sk_function_with_name_specified(): +def test_kernel_function_with_name_specified(): decorator_test = MiscClass() my_func = getattr(decorator_test, "func_with_name") - assert my_func.__sk_function_name__ == "my-name" + assert my_func.__kernel_function_name__ == "my-name" diff --git a/python/tests/unit/template_engine/blocks/test_code_block.py b/python/tests/unit/template_engine/blocks/test_code_block.py index f007ee45b8db..348cb1156618 100644 --- a/python/tests/unit/template_engine/blocks/test_code_block.py +++ b/python/tests/unit/template_engine/blocks/test_code_block.py @@ -5,8 +5,8 @@ from semantic_kernel.memory.null_memory import NullMemory from semantic_kernel.orchestration.context_variables import ContextVariables from semantic_kernel.orchestration.delegate_types import DelegateTypes -from semantic_kernel.orchestration.sk_context import SKContext -from semantic_kernel.orchestration.sk_function import SKFunction +from semantic_kernel.orchestration.kernel_context import KernelContext +from semantic_kernel.orchestration.kernel_function import KernelFunction from semantic_kernel.plugin_definition.read_only_plugin_collection_base import ( ReadOnlyPluginCollectionBase, ) @@ -23,7 +23,7 @@ def setup_method(self): @mark.asyncio async def test_it_throws_if_a_function_doesnt_exist(self): - context = SKContext.model_construct( + context = KernelContext.model_construct( variables=ContextVariables(), memory=NullMemory(), plugin_collection=self.plugins, @@ -39,7 +39,7 @@ async def test_it_throws_if_a_function_doesnt_exist(self): @mark.asyncio async def test_it_throws_if_a_function_call_throws(self): - context = SKContext.model_construct( + context = KernelContext.model_construct( variables=ContextVariables(), memory=NullMemory(), plugin_collection=self.plugins, @@ -48,8 +48,8 @@ async def test_it_throws_if_a_function_call_throws(self): def invoke(_): raise Exception("error") - function = SKFunction( - delegate_type=DelegateTypes.InSKContext, + function = KernelFunction( + delegate_type=DelegateTypes.InKernelContext, delegate_function=invoke, plugin_name="", function_name="funcName", @@ -145,7 +145,7 @@ async def test_it_renders_code_block_consisting_of_just_a_var_block1(self): variables = ContextVariables() variables["varName"] = "foo" - context = SKContext.model_construct( + context = KernelContext.model_construct( variables=variables, memory=NullMemory(), plugin_collection=None, @@ -163,7 +163,7 @@ async def test_it_renders_code_block_consisting_of_just_a_var_block2(self): variables = ContextVariables() variables["varName"] = "bar" - context = SKContext.model_construct( + context = KernelContext.model_construct( variables=variables, memory=NullMemory(), plugin_collection=None, @@ -179,7 +179,7 @@ async def test_it_renders_code_block_consisting_of_just_a_var_block2(self): @mark.asyncio async def test_it_renders_code_block_consisting_of_just_a_val_block1(self): - context = SKContext.model_construct( + context = KernelContext.model_construct( variables=ContextVariables(), memory=NullMemory(), plugin_collection=None, @@ -194,7 +194,7 @@ async def test_it_renders_code_block_consisting_of_just_a_val_block1(self): @mark.asyncio async def test_it_renders_code_block_consisting_of_just_a_val_block2(self): - context = SKContext.model_construct( + context = KernelContext.model_construct( variables=ContextVariables(), memory=NullMemory(), plugin_collection=None, @@ -217,7 +217,7 @@ async def test_it_invokes_function_cloning_all_variables(self): variables["var2"] = "due" # Create a context with the variables, memory, and plugin collection - context = SKContext.model_construct( + context = KernelContext.model_construct( variables=variables, memory=NullMemory(), plugin_collection=self.plugins, @@ -241,9 +241,9 @@ def invoke(ctx): ctx["var1"] = "overridden" ctx["var2"] = "overridden" - # Create an SKFunction with the invoke function as its delegate - function = SKFunction( - delegate_type=DelegateTypes.InSKContext, + # Create an KernelFunction with the invoke function as its delegate + function = KernelFunction( + delegate_type=DelegateTypes.InKernelContext, delegate_function=invoke, plugin_name="", function_name="funcName", @@ -284,7 +284,7 @@ async def test_it_invokes_function_with_custom_variable(self): variables[VAR_NAME] = VAR_VALUE # Create a context with the variables, memory, and plugin collection - context = SKContext.model_construct( + context = KernelContext.model_construct( variables=variables, memory=NullMemory(), plugin_collection=self.plugins, @@ -303,9 +303,9 @@ def invoke(ctx): nonlocal canary canary = ctx["input"] - # Create an SKFunction with the invoke function as its delegate - function = SKFunction( - delegate_type=DelegateTypes.InSKContext, + # Create an KernelFunction with the invoke function as its delegate + function = KernelFunction( + delegate_type=DelegateTypes.InKernelContext, delegate_function=invoke, plugin_name="", function_name="funcName", @@ -337,7 +337,7 @@ async def test_it_invokes_function_with_custom_value(self): VALUE = "value" # Create a context with empty variables, memory, and plugin collection - context = SKContext.model_construct( + context = KernelContext.model_construct( variables=ContextVariables(), memory=NullMemory(), plugin_collection=self.plugins, @@ -355,9 +355,9 @@ def invoke(ctx): nonlocal canary canary = ctx["input"] - # Create an SKFunction with the invoke function as its delegate - function = SKFunction( - delegate_type=DelegateTypes.InSKContext, + # Create an KernelFunction with the invoke function as its delegate + function = KernelFunction( + delegate_type=DelegateTypes.InKernelContext, delegate_function=invoke, plugin_name="", function_name="funcName", diff --git a/python/tests/unit/template_engine/test_prompt_template_engine.py b/python/tests/unit/template_engine/test_prompt_template_engine.py index 6e8ee2f4e171..a7ad707eb541 100644 --- a/python/tests/unit/template_engine/test_prompt_template_engine.py +++ b/python/tests/unit/template_engine/test_prompt_template_engine.py @@ -6,9 +6,9 @@ from semantic_kernel.memory.null_memory import NullMemory from semantic_kernel.orchestration.context_variables import ContextVariables -from semantic_kernel.orchestration.sk_context import SKContext -from semantic_kernel.orchestration.sk_function import SKFunction -from semantic_kernel.plugin_definition import sk_function +from semantic_kernel.orchestration.kernel_context import KernelContext +from semantic_kernel.orchestration.kernel_function import KernelFunction +from semantic_kernel.plugin_definition import kernel_function from semantic_kernel.plugin_definition.read_only_plugin_collection import ( ReadOnlyPluginCollection, ) @@ -33,7 +33,7 @@ def plugins(): @fixture def context(variables, plugins): - return SKContext(variables, NullMemory(), plugins) + return KernelContext(variables, NullMemory(), plugins) def test_it_renders_variables(target: PromptTemplateEngine, variables: ContextVariables): @@ -122,11 +122,11 @@ async def test_it_renders_code_using_input_async( variables: ContextVariables, context_factory, ): - @sk_function(name="function") - def my_function_async(cx: SKContext) -> str: + @kernel_function(name="function") + def my_function_async(cx: KernelContext) -> str: return f"F({cx.variables.input})" - func = SKFunction.from_native_method(my_function_async) + func = KernelFunction.from_native_method(my_function_async) assert func is not None variables.update("INPUT-BAR") @@ -142,11 +142,11 @@ async def test_it_renders_code_using_variables_async( variables: ContextVariables, context_factory, ): - @sk_function(name="function") - def my_function_async(cx: SKContext) -> str: + @kernel_function(name="function") + def my_function_async(cx: KernelContext) -> str: return f"F({cx.variables.input})" - func = SKFunction.from_native_method(my_function_async) + func = KernelFunction.from_native_method(my_function_async) assert func is not None variables.set("myVar", "BAR") @@ -162,11 +162,11 @@ async def test_it_renders_async_code_using_variables_async( variables: ContextVariables, context_factory, ): - @sk_function(name="function") - async def my_function_async(cx: SKContext) -> str: + @kernel_function(name="function") + async def my_function_async(cx: KernelContext) -> str: return cx.variables.input - func = SKFunction.from_native_method(my_function_async) + func = KernelFunction.from_native_method(my_function_async) assert func is not None variables.set("myVar", "BAR") diff --git a/python/tests/unit/test_kernel.py b/python/tests/unit/test_kernel.py index b9c1d90f73ae..6a0a80c4e2d0 100644 --- a/python/tests/unit/test_kernel.py +++ b/python/tests/unit/test_kernel.py @@ -5,13 +5,13 @@ import pytest from semantic_kernel import Kernel -from semantic_kernel.orchestration.sk_function_base import SKFunctionBase +from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase from semantic_kernel.plugin_definition.function_view import FunctionView -def create_mock_function(name) -> SKFunctionBase: +def create_mock_function(name) -> KernelFunctionBase: function_view = FunctionView(name, "SummarizePlugin", "Summarize an input", [], True, True) - mock_function = Mock(spec=SKFunctionBase) + mock_function = Mock(spec=KernelFunctionBase) mock_function.describe.return_value = function_view mock_function.name = function_view.name mock_function.plugin_name = function_view.plugin_name diff --git a/python/tests/unit/test_serialization.py b/python/tests/unit/test_serialization.py index f92c4f7f08e2..7c7ce8b9ec61 100644 --- a/python/tests/unit/test_serialization.py +++ b/python/tests/unit/test_serialization.py @@ -4,7 +4,7 @@ import typing_extensions as te from pydantic import Field, Json -from semantic_kernel import SKFunctionBase +from semantic_kernel import KernelFunctionBase from semantic_kernel.core_plugins.conversation_summary_plugin import ( ConversationSummaryPlugin, ) @@ -16,15 +16,17 @@ from semantic_kernel.core_plugins.time_plugin import TimePlugin from semantic_kernel.core_plugins.wait_plugin import WaitPlugin from semantic_kernel.core_plugins.web_search_engine_plugin import WebSearchEnginePlugin +from semantic_kernel.kernel_pydantic import KernelBaseModel from semantic_kernel.memory.null_memory import NullMemory from semantic_kernel.memory.semantic_text_memory_base import SemanticTextMemoryBase from semantic_kernel.orchestration.context_variables import ContextVariables from semantic_kernel.orchestration.delegate_handlers import DelegateHandlers from semantic_kernel.orchestration.delegate_inference import DelegateInference -from semantic_kernel.orchestration.sk_context import SKContext -from semantic_kernel.orchestration.sk_function import SKFunction +from semantic_kernel.orchestration.kernel_context import KernelContext +from semantic_kernel.orchestration.kernel_function import KernelFunction from semantic_kernel.plugin_definition.function_view import FunctionView from semantic_kernel.plugin_definition.functions_view import FunctionsView +from semantic_kernel.plugin_definition.kernel_function_decorator import kernel_function from semantic_kernel.plugin_definition.parameter_view import ParameterView from semantic_kernel.plugin_definition.plugin_collection import PluginCollection from semantic_kernel.plugin_definition.plugin_collection_base import ( @@ -36,8 +38,6 @@ from semantic_kernel.plugin_definition.read_only_plugin_collection_base import ( ReadOnlyPluginCollectionBase, ) -from semantic_kernel.plugin_definition.sk_function_decorator import sk_function -from semantic_kernel.sk_pydantic import SKBaseModel from semantic_kernel.template_engine.blocks.block import Block from semantic_kernel.template_engine.blocks.block_types import BlockTypes from semantic_kernel.template_engine.blocks.code_block import CodeBlock @@ -54,7 +54,7 @@ from semantic_kernel.template_engine.protocols.text_renderer import TextRenderer from semantic_kernel.template_engine.template_tokenizer import TemplateTokenizer -SKBaseModelFieldT = t.TypeVar("SKBaseModelFieldT", bound=SKBaseModel) +KernelBaseModelFieldT = t.TypeVar("KernelBaseModelFieldT", bound=KernelBaseModel) class _Serializable(t.Protocol): @@ -71,7 +71,7 @@ def parse_raw(cls: t.Type[te.Self], json: Json) -> te.Self: @pytest.fixture() -def sk_factory() -> t.Callable[[t.Type[_Serializable]], _Serializable]: +def kernel_factory() -> t.Callable[[t.Type[_Serializable]], _Serializable]: """Return a factory for various objects in semantic-kernel.""" def create_functions_view() -> FunctionsView: @@ -99,14 +99,14 @@ def create_functions_view() -> FunctionsView: ) return result - def create_sk_function() -> SKFunction: - """Return an SKFunction.""" + def create_kernel_function() -> KernelFunction: + """Return an KernelFunction.""" - @sk_function(name="function") - def my_function_async(cx: SKContext) -> str: + @kernel_function(name="function") + def my_function_async(cx: KernelContext) -> str: return f"F({cx.variables.input})" - return SKFunction.from_native_method(my_function_async) + return KernelFunction.from_native_method(my_function_async) def create_context_variables() -> ContextVariables: """Return a context variables object.""" @@ -151,14 +151,14 @@ def create_plugin_collection() -> PluginCollection: DelegateInference: DelegateInference(), ContextVariables: create_context_variables(), PluginCollection: create_plugin_collection(), - SKContext[NullMemory]: SKContext[NullMemory]( + KernelContext[NullMemory]: KernelContext[NullMemory]( # TODO: Test serialization with different types of memories. variables=create_context_variables(), memory=NullMemory(), plugin_collection=create_plugin_collection().read_only_plugin_collection, ), NullMemory: NullMemory(), - SKFunction: create_sk_function(), + KernelFunction: create_kernel_function(), } def constructor(cls: t.Type[_Serializable]) -> _Serializable: @@ -187,7 +187,7 @@ def constructor(cls: t.Type[_Serializable]) -> _Serializable: ReadOnlyPluginCollectionBase, PluginCollectionBase, SemanticTextMemoryBase, - SKFunctionBase, + KernelFunctionBase, ] # Classes that don't need serialization @@ -222,9 +222,9 @@ def constructor(cls: t.Type[_Serializable]) -> _Serializable: ReadOnlyPluginCollection, PluginCollection, ContextVariables, - SKContext[NullMemory], + KernelContext[NullMemory], pytest.param( - SKFunction, + KernelFunction, marks=pytest.mark.xfail(reason="Need to implement Pickle serialization."), ), ] @@ -232,43 +232,43 @@ def constructor(cls: t.Type[_Serializable]) -> _Serializable: class TestUsageInPydanticFields: @pytest.mark.parametrize( - "sk_type", + "kernel_type", BASE_CLASSES + PROTOCOLS + ENUMS + PYDANTIC_MODELS + STATELESS_CLASSES + UNSERIALIZED_CLASSES, ) def test_usage_as_optional_field( self, - sk_type: t.Type[SKBaseModelFieldT], + kernel_type: t.Type[KernelBaseModelFieldT], ) -> None: """Semantic Kernel objects should be valid Pydantic fields. Otherwise, they cannot be used in Pydantic models. """ - class TestModel(SKBaseModel): + class TestModel(KernelBaseModel): """A test model.""" - field: t.Optional[sk_type] = None + field: t.Optional[kernel_type] = None assert_serializable(TestModel(), TestModel) - @pytest.mark.parametrize("sk_type", PYDANTIC_MODELS + STATELESS_CLASSES) + @pytest.mark.parametrize("kernel_type", PYDANTIC_MODELS + STATELESS_CLASSES) def test_usage_as_required_field( self, - sk_factory: t.Callable[[t.Type[SKBaseModelFieldT]], SKBaseModelFieldT], - sk_type: t.Type[SKBaseModelFieldT], + kernel_factory: t.Callable[[t.Type[KernelBaseModelFieldT]], KernelBaseModelFieldT], + kernel_type: t.Type[KernelBaseModelFieldT], ) -> None: """Semantic Kernel objects should be valid Pydantic fields. Otherwise, they cannot be used in Pydantic models. """ - class TestModel(SKBaseModel): + class TestModel(KernelBaseModel): """A test model.""" - field: sk_type = Field(default_factory=lambda: sk_factory(sk_type)) + field: kernel_type = Field(default_factory=lambda: kernel_factory(kernel_type)) assert_serializable(TestModel(), TestModel) - assert_serializable(TestModel(field=sk_factory(sk_type)), TestModel) + assert_serializable(TestModel(field=kernel_factory(kernel_type)), TestModel) def assert_serializable(obj: _Serializable, obj_type) -> None: