From 6a8a5c0c2a8210ef5ed98469829835c1269d40b4 Mon Sep 17 00:00:00 2001 From: Niels Swimberghe <3382717+Swimburger@users.noreply.github.com> Date: Mon, 23 Oct 2023 23:42:06 -0400 Subject: [PATCH] Update with SK 1.0.0-beta3 & simplify plugin API --- README.md | 46 ++++-------- .../AssemblyAI.SemanticKernel.csproj | 2 +- .../TranscriptPlugin.cs | 73 +++++++------------ src/Sample/FindFilePlugin.cs | 22 ++---- src/Sample/Program.cs | 36 ++++----- src/Sample/Sample.csproj | 2 +- 6 files changed, 66 insertions(+), 115 deletions(-) diff --git a/README.md b/README.md index 8ece858..d32d9a3 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,6 @@ Next, register the `TranscriptPlugin` into your kernel: ```csharp using AssemblyAI.SemanticKernel; using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Orchestration; // Build your kernel var kernel = new KernelBuilder().Build(); @@ -36,7 +35,7 @@ var kernel = new KernelBuilder().Build(); string apiKey = Environment.GetEnvironmentVariable("ASSEMBLYAI_API_KEY") ?? throw new Exception("ASSEMBLYAI_API_KEY env variable not configured."); -var transcriptPlugin = kernel.ImportSkill( +kernel.ImportFunctions( new TranscriptPlugin(apiKey: apiKey), TranscriptPlugin.PluginName ); @@ -46,47 +45,37 @@ var transcriptPlugin = kernel.ImportSkill( Get the `Transcribe` function from the transcript plugin and invoke it with the context variables. ```csharp -var function = kernel.Skills +var function = kernel.Functions .GetFunction(TranscriptPlugin.PluginName, TranscriptPlugin.TranscribeFunctionName); var context = kernel.CreateNewContext(); -context.Variables["audioUrl"] = "https://storage.googleapis.com/aai-docs-samples/espn.m4a"; -await function.InvokeAsync(context); -Console.WriteLine(context.Result); +context.Variables["INPUT"] = "https://storage.googleapis.com/aai-docs-samples/espn.m4a"; +var result = await function.InvokeAsync(context); +Console.WriteLine(result.GetValue()`. You can also upload local audio and video file. To do this: -- Set the `TranscriptPlugin.AllowFileSystemAccess` property to `true` -- Configure the path of the file to upload as the `filePath` parameter +- Set the `TranscriptPlugin.AllowFileSystemAccess` property to `true`. +- Configure the `INPUT` variable with a local file path. ```csharp -var transcriptPlugin = kernel.ImportSkill( +kernel.ImportFunctions( new TranscriptPlugin(apiKey: apiKey) { AllowFileSystemAccess = true }, TranscriptPlugin.PluginName ); -var function = kernel.Skills +var function = kernel.Functions .GetFunction(TranscriptPlugin.PluginName, TranscriptPlugin.TranscribeFunctionName); var context = kernel.CreateNewContext(); -context.Variables["filePath"] = "./espn.m4a"; -await function.InvokeAsync(context); -Console.WriteLine(context.Result); +context.Variables["INPUT"] = "./espn.m4a"; +var result = await function.InvokeAsync(context); +Console.WriteLine(result.GetValue()); ``` -If `filePath` and `audioUrl` are specified, the `filePath` will be used to upload the file and `audioUrl` will be overridden. - -Lastly, you can also use the `INPUT` variable, so you can transcribe a file like this. - -```csharp -var function = kernel.Skills - .GetFunction(TranscriptPlugin.PluginName, TranscriptPlugin.TranscribeFunctionName); -var context = await function.InvokeAsync("./espn.m4a"); -``` - -Or from within a semantic function like this. +You can also invoke the function from within a semantic function like this. ```csharp var prompt = """ @@ -97,13 +86,10 @@ var prompt = """ """; var context = kernel.CreateNewContext(); var function = kernel.CreateSemanticFunction(prompt); -await function.InvokeAsync(context); -Console.WriteLine(context.Result); +var result = await function.InvokeAsync(context); +Console.WriteLine(result.GetValue()); ``` -If the `INPUT` variable is a URL, it'll be used as the `audioUrl`, otherwise, it'll be used as the `filePath`. -If either `audioUrl` or `filePath` are configured, `INPUT` is ignored. - All the code above explicitly invokes the transcript plugin, but it can also be invoked as part of a plan. Check out [the Sample project](./src/Sample/Program.cs#L96)) which uses a plan to transcribe an audio file in addition to explicit invocation. diff --git a/src/AssemblyAI.SemanticKernel/AssemblyAI.SemanticKernel.csproj b/src/AssemblyAI.SemanticKernel/AssemblyAI.SemanticKernel.csproj index 4caedea..d192a40 100644 --- a/src/AssemblyAI.SemanticKernel/AssemblyAI.SemanticKernel.csproj +++ b/src/AssemblyAI.SemanticKernel/AssemblyAI.SemanticKernel.csproj @@ -32,7 +32,7 @@ - 0.24.230918.1-preview + 1.0.0-beta3 7.0.1 diff --git a/src/AssemblyAI.SemanticKernel/TranscriptPlugin.cs b/src/AssemblyAI.SemanticKernel/TranscriptPlugin.cs index ae60cf5..ee37c93 100644 --- a/src/AssemblyAI.SemanticKernel/TranscriptPlugin.cs +++ b/src/AssemblyAI.SemanticKernel/TranscriptPlugin.cs @@ -7,8 +7,7 @@ using System.Text; using System.Text.Json; using System.Threading.Tasks; -using Microsoft.SemanticKernel.Orchestration; -using Microsoft.SemanticKernel.SkillDefinition; +using Microsoft.SemanticKernel; namespace AssemblyAI.SemanticKernel { @@ -26,19 +25,21 @@ public TranscriptPlugin(string apiKey) public const string TranscribeFunctionName = nameof(Transcribe); [SKFunction, Description("Transcribe an audio or video file to text.")] - [SKParameter("filePath", @"The path of the audio or video file. -If filePath is configured, the file will be uploaded to AssemblyAI, and then used as the audioUrl to transcribe. -Optional if audioUrl is configured. The uploaded file will override the audioUrl parameter.")] - [SKParameter("audioUrl", @"The public URL of the audio or video file to transcribe. -Optional if filePath is configured.")] - public async Task Transcribe(SKContext context) + public async Task Transcribe( + [Description("The public URL or the local path of the audio or video file to transcribe.")] + string input + ) { - SetPathAndUrl(context, out var filePath, out var audioUrl); + if (string.IsNullOrEmpty(input)) + { + throw new Exception("The INPUT parameter is required."); + } + using (var httpClient = new HttpClient()) { httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(_apiKey); - - if (filePath != null) + string audioUrl; + if (TryGetPath(input, out var filePath)) { if (AllowFileSystemAccess == false) { @@ -49,6 +50,10 @@ public async Task Transcribe(SKContext context) audioUrl = await UploadFileAsync(filePath, httpClient); } + else + { + audioUrl = input; + } var transcript = await CreateTranscriptAsync(audioUrl, httpClient); transcript = await WaitForTranscriptToProcess(transcript, httpClient); @@ -56,50 +61,22 @@ public async Task Transcribe(SKContext context) } } - private static void SetPathAndUrl(SKContext context, out string filePath, out string audioUrl) + private static bool TryGetPath(string input, out string filePath) { - filePath = null; - audioUrl = null; - if (context.Variables.TryGetValue("filePath", out filePath)) - { - return; - } - - if (context.Variables.TryGetValue("audioUrl", out audioUrl)) - { - var uri = new Uri(audioUrl); - if (uri.IsFile) - { - filePath = uri.LocalPath; - audioUrl = null; - } - else - { - return; - } - } - - context.Variables.TryGetValue("INPUT", out var input); - if (input == null) - { - throw new Exception("You must pass in INPUT, filePath, or audioUrl parameter."); - } - if (Uri.TryCreate(input, UriKind.Absolute, out var inputUrl)) { if (inputUrl.IsFile) { filePath = inputUrl.LocalPath; + return true; } - else - { - audioUrl = input; - } - } - else - { - filePath = input; + + filePath = null; + return false; } + + filePath = input; + return true; } private static async Task UploadFileAsync(string path, HttpClient httpClient) @@ -128,7 +105,7 @@ private static async Task CreateTranscriptAsync(string audioUrl, Htt using (var response = await httpClient.PostAsync("https://api.assemblyai.com/v2/transcript", content)) { response.EnsureSuccessStatusCode(); - var transcript = (await response.Content.ReadFromJsonAsync()); + var transcript = await response.Content.ReadFromJsonAsync(); if (transcript.Status == "error") throw new Exception(transcript.Error); return transcript; } diff --git a/src/Sample/FindFilePlugin.cs b/src/Sample/FindFilePlugin.cs index 8bf8553..7acc675 100644 --- a/src/Sample/FindFilePlugin.cs +++ b/src/Sample/FindFilePlugin.cs @@ -1,16 +1,15 @@ using System.ComponentModel; using System.Text.RegularExpressions; using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Orchestration; -using Microsoft.SemanticKernel.SkillDefinition; namespace AssemblyAI.SemanticKernel.Sample; public class FindFilePlugin { - public const string PluginName = "FindFilePlugin"; + public const string PluginName = nameof(FindFilePlugin); private readonly IKernel _kernel; + public FindFilePlugin(IKernel kernel) { _kernel = kernel; @@ -22,27 +21,22 @@ public FindFilePlugin(IKernel kernel) $"on operating platform {Environment.OSVersion.Platform.ToString()} " + $"with user profile path '{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}' is "; var context = await _kernel.InvokeSemanticFunctionAsync( - promptTemplate: prompt, - temperature: 0 + template: prompt ); var matches = Regex.Matches( - context.Result, + context.GetValue()!, @"([a-zA-Z]?\:?[\/\\][\S-[""'\. ]]*[\/\\][\S-[""'\. ]]*)", RegexOptions.IgnoreCase ); return matches.LastOrDefault()?.Value ?? null; } - - public const string LocateFileFunctionName = nameof(LocateFile); - [SKFunction, Description("Find files in common folders.")] - [SKParameter("fileName", "The name of the file")] - [SKParameter("commonFolderName", "The name of the common folder")] - public async Task LocateFile(SKContext context) + public async Task LocateFile( + [Description("The name of the file")] string fileName, + [Description("The name of the common folder")] + string? commonFolderName) { - var fileName = context.Variables["fileName"]; - var commonFolderName = context.Variables["commonFolderName"]; var commonFolderPath = commonFolderName?.ToLower() switch { null => Environment.CurrentDirectory, diff --git a/src/Sample/Program.cs b/src/Sample/Program.cs index b0261be..69478ef 100644 --- a/src/Sample/Program.cs +++ b/src/Sample/Program.cs @@ -2,8 +2,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Orchestration; -using Microsoft.SemanticKernel.Planning; +using Microsoft.SemanticKernel.Planners; namespace AssemblyAI.SemanticKernel.Sample; @@ -16,8 +15,8 @@ public static async Task Main(string[] args) var kernel = BuildKernel(config); await TranscribeFileUsingPluginDirectly(kernel); - //await TranscribeFileUsingPluginFromSemanticFunction(kernel); - //await TranscribeFileUsingPlan(kernel); + await TranscribeFileUsingPluginFromSemanticFunction(kernel); + await TranscribeFileUsingPlan(kernel); } private static IKernel BuildKernel(IConfiguration config) @@ -33,7 +32,7 @@ private static IKernel BuildKernel(IConfiguration config) var apiKey = config["AssemblyAI:ApiKey"] ?? throw new Exception("AssemblyAI:ApiKey configuration is required."); - kernel.ImportSkill( + kernel.ImportFunctions( new TranscriptPlugin(apiKey: apiKey) { AllowFileSystemAccess = true @@ -41,8 +40,8 @@ private static IKernel BuildKernel(IConfiguration config) TranscriptPlugin.PluginName ); - kernel.ImportSkill( - new FindFilePlugin(kernel: kernel), + kernel.ImportFunctions( + new FindFilePlugin(kernel), FindFilePlugin.PluginName ); return kernel; @@ -61,17 +60,13 @@ private static IConfigurationRoot BuildConfig(string[] args) private static async Task TranscribeFileUsingPluginDirectly(IKernel kernel) { Console.WriteLine("Transcribing file using plugin directly"); - var variables = new ContextVariables - { - ["audioUrl"] = "https://storage.googleapis.com/aai-docs-samples/espn.m4a", - // ["filePath"] = "./espn.m4a" // you can also use `filePath` which will upload the file and override `audioUrl` - }; - - var result = await kernel.Skills + var context = kernel.CreateNewContext(); + context.Variables["INPUT"] = "https://storage.googleapis.com/aai-docs-samples/espn.m4a"; + var result = await kernel.Functions .GetFunction(TranscriptPlugin.PluginName, TranscriptPlugin.TranscribeFunctionName) - .InvokeAsync(variables); + .InvokeAsync(context); - Console.WriteLine(result.Result); + Console.WriteLine(result.GetValue()); Console.WriteLine(); } @@ -88,8 +83,8 @@ Summarize the transcript. """; var context = kernel.CreateNewContext(); var function = kernel.CreateSemanticFunction(prompt); - await function.InvokeAsync(context); - Console.WriteLine(context.Result); + var result = await function.InvokeAsync(context); + Console.WriteLine(result.GetValue()); Console.WriteLine(); } @@ -97,14 +92,13 @@ private static async Task TranscribeFileUsingPlan(IKernel kernel) { Console.WriteLine("Transcribing file from a plan"); var planner = new SequentialPlanner(kernel); - - const string prompt = "Transcribe the espn.m4a in my downloads folder."; + const string prompt = "Find the espn.m4a in my downloads folder and transcribe it."; var plan = await planner.CreatePlanAsync(prompt); Console.WriteLine("Plan:\n"); Console.WriteLine(JsonSerializer.Serialize(plan, new JsonSerializerOptions { WriteIndented = true })); - var transcript = (await kernel.RunAsync(plan)).Result; + var transcript = (await kernel.RunAsync(plan)).GetValue(); Console.WriteLine(transcript); Console.WriteLine(); } diff --git a/src/Sample/Sample.csproj b/src/Sample/Sample.csproj index f01da71..8413b39 100644 --- a/src/Sample/Sample.csproj +++ b/src/Sample/Sample.csproj @@ -33,7 +33,7 @@ 7.0.0 - 0.24.230918.1-preview + 1.0.0-beta3