From 763f15ebf844ac9b5d1da088ce8fda3d5cbf54a8 Mon Sep 17 00:00:00 2001 From: Gil LaHaye Date: Tue, 14 Mar 2023 20:45:06 -0700 Subject: [PATCH 1/4] Add FileCompression skill --- dotnet/SK-dotnet.sln | 7 + .../FileCompression/FileCompressionTests.cs | 140 ++++++++++++++++ .../SemanticKernel.IntegrationTests.csproj | 1 + .../FileCompressionSkill.cs | 158 ++++++++++++++++++ .../Skills.FileCompression/IFileCompressor.cs | 39 +++++ .../Skills.FileCompression.csproj | 24 +++ .../ZipFileCompressor.cs | 44 +++++ .../Skills.Web/WebFileDownloadSkill.cs | 2 +- 8 files changed, 414 insertions(+), 1 deletion(-) create mode 100644 dotnet/src/SemanticKernel.IntegrationTests/FileCompression/FileCompressionTests.cs create mode 100644 dotnet/src/SemanticKernel.Skills/Skills.FileCompression/FileCompressionSkill.cs create mode 100644 dotnet/src/SemanticKernel.Skills/Skills.FileCompression/IFileCompressor.cs create mode 100644 dotnet/src/SemanticKernel.Skills/Skills.FileCompression/Skills.FileCompression.csproj create mode 100644 dotnet/src/SemanticKernel.Skills/Skills.FileCompression/ZipFileCompressor.cs diff --git a/dotnet/SK-dotnet.sln b/dotnet/SK-dotnet.sln index bbdf5984aec2..481df8505a58 100644 --- a/dotnet/SK-dotnet.sln +++ b/dotnet/SK-dotnet.sln @@ -52,6 +52,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nuget", "nuget", "{F4243136 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KernelBuilder", "..\samples\dotnet\KernelBuilder\KernelBuilder.csproj", "{A52818AC-57FB-495F-818F-9E1E7BC5618C}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Skills.FileCompression", "src\SemanticKernel.Skills\Skills.FileCompression\Skills.FileCompression.csproj", "{5D9466B9-6E9F-4337-AC70-1CE8F08385F7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -110,6 +112,10 @@ Global {A52818AC-57FB-495F-818F-9E1E7BC5618C}.Debug|Any CPU.Build.0 = Debug|Any CPU {A52818AC-57FB-495F-818F-9E1E7BC5618C}.Release|Any CPU.ActiveCfg = Release|Any CPU {A52818AC-57FB-495F-818F-9E1E7BC5618C}.Release|Any CPU.Build.0 = Release|Any CPU + {5D9466B9-6E9F-4337-AC70-1CE8F08385F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D9466B9-6E9F-4337-AC70-1CE8F08385F7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D9466B9-6E9F-4337-AC70-1CE8F08385F7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D9466B9-6E9F-4337-AC70-1CE8F08385F7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -130,6 +136,7 @@ Global {107156B4-5A8B-45C7-97A2-4544D7FA19DE} = {9ECD1AA0-75B3-4E25-B0B5-9F0945B64974} {F4243136-252A-4459-A7C4-EE8C056D6B0B} = {158A4E5E-AEE0-4D60-83C7-8E089B2D881D} {A52818AC-57FB-495F-818F-9E1E7BC5618C} = {FA3720F1-C99A-49B2-9577-A940257098BF} + {5D9466B9-6E9F-4337-AC70-1CE8F08385F7} = {9ECD1AA0-75B3-4E25-B0B5-9F0945B64974} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FBDC56A3-86AD-4323-AA0F-201E59123B83} diff --git a/dotnet/src/SemanticKernel.IntegrationTests/FileCompression/FileCompressionTests.cs b/dotnet/src/SemanticKernel.IntegrationTests/FileCompression/FileCompressionTests.cs new file mode 100644 index 000000000000..097e3a77f2df --- /dev/null +++ b/dotnet/src/SemanticKernel.IntegrationTests/FileCompression/FileCompressionTests.cs @@ -0,0 +1,140 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.IO; +using System.Threading.Tasks; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Orchestration; +using Microsoft.SemanticKernel.Skills.FileCompression; +using Microsoft.SemanticKernel.Skills.Web; +using SemanticKernel.IntegrationTests; +using Xunit; +using Xunit.Abstractions; + +namespace IntegrationTests.FileCompression; + +public class FileCompressionTests : IDisposable +{ + public FileCompressionTests(ITestOutputHelper output) + { + this._logger = new XunitLogger(output); + this._output = output; + + this._testOutputHelper = new RedirectOutput(output); + Console.SetOut(this._testOutputHelper); + } + + [Fact] + public async Task ZipFileCompressionAndDecompressionTestAsync() + { + // Arrange - Create objects + IKernel kernel = Kernel.Builder.WithLogger(this._logger).Build(); + using XunitLogger skillLogger = new(this._output); + var zipCompressor = new ZipFileCompressor(); + var skill = new FileCompressionSkill(zipCompressor); + var fileCompression = kernel.ImportSkill(skill, "FileCompression"); + + // Arrange - Create file to compress + string tempPath = Path.GetTempPath(); + string tempFileName = Path.GetRandomFileName(); + var sourceFilePath = Path.Join(tempPath, tempFileName); + await File.WriteAllTextAsync(sourceFilePath, new string('*', 100)); + + var destinationFilePath = sourceFilePath + ".zip"; + var contextVariables = new ContextVariables(sourceFilePath); + contextVariables.Set(FileCompressionSkill.Parameters.DestinationFilePath, destinationFilePath); + + // Act - Compress file and decompress it + await kernel.RunAsync(contextVariables, fileCompression["CompressFileAsync"]); + string uncompressedFilePath = sourceFilePath + ".original"; + File.Move(sourceFilePath, uncompressedFilePath); + contextVariables = new ContextVariables(destinationFilePath); + contextVariables.Set(FileCompressionSkill.Parameters.DestinationDirectoryPath, tempPath); + await kernel.RunAsync(contextVariables, fileCompression["DecompressFileAsync"]); + + // Assert + string uncompressedFileContents = await File.ReadAllTextAsync(uncompressedFilePath); + string decompressedFilePath = sourceFilePath; + string decompressedFileContents = await File.ReadAllTextAsync(uncompressedFilePath); + Assert.Equal(uncompressedFileContents, decompressedFileContents); + + // Clean up + File.Delete(destinationFilePath); // Zip file + File.Delete(uncompressedFilePath); + File.Delete(decompressedFilePath); + } + + [Fact] + public async Task ZipDirectoryCompressionAndDecompressionTestAsync() + { + // Arrange - Create objects + const string File1 = "file1.txt"; + const string File2 = "file2.txt"; + IKernel kernel = Kernel.Builder.WithLogger(this._logger).Build(); + using XunitLogger skillLogger = new(this._output); + var zipCompressor = new ZipFileCompressor(); + var skill = new FileCompressionSkill(zipCompressor); + var fileCompression = kernel.ImportSkill(skill, "FileCompression"); + + // Arrange - Create a folder with 2 files to compress + string tempPath = Path.GetTempPath(); + string tempSubDirectoryName = Path.GetRandomFileName(); + string directoryToCompress = Path.Join(tempPath, tempSubDirectoryName); + Directory.CreateDirectory(directoryToCompress); + var sourceFilePath1 = Path.Join(directoryToCompress, File1); + await File.WriteAllTextAsync(sourceFilePath1, new string('*', 100)); + var sourceFilePath2 = Path.Join(directoryToCompress, File2); + await File.WriteAllTextAsync(sourceFilePath2, new string('*', 200)); + var destinationFilePath = Path.Join(tempPath, tempSubDirectoryName + ".zip"); + + var contextVariables = new ContextVariables(directoryToCompress); + contextVariables.Set(FileCompressionSkill.Parameters.DestinationFilePath, destinationFilePath); + + // Act - Compress folder and decompress it + await kernel.RunAsync(contextVariables, fileCompression["CompressDirectoryAsync"]); + string decompressedFilesDirectory = Path.Join(tempPath, tempSubDirectoryName + "decompressed"); + contextVariables = new ContextVariables(destinationFilePath); + contextVariables.Set(FileCompressionSkill.Parameters.DestinationDirectoryPath, decompressedFilesDirectory); + await kernel.RunAsync(contextVariables, fileCompression["DecompressFileAsync"]); + + // Assert + string uncompressedFile1Contents = await File.ReadAllTextAsync(sourceFilePath1); + string uncompressedFile2Contents = await File.ReadAllTextAsync(sourceFilePath2); + string decompressedFile1Contents = await File.ReadAllTextAsync(Path.Join(decompressedFilesDirectory, File1)); + string decompressedFile2Contents = await File.ReadAllTextAsync(Path.Join(decompressedFilesDirectory, File2)); + Assert.Equal(uncompressedFile1Contents, decompressedFile1Contents); + Assert.Equal(uncompressedFile2Contents, decompressedFile2Contents); + + // Cleanup + File.Delete(destinationFilePath); // Zip file + Directory.Delete(directoryToCompress, recursive: true); + Directory.Delete(decompressedFilesDirectory, recursive: true); + } + + private readonly XunitLogger _logger; + private readonly ITestOutputHelper _output; + private readonly RedirectOutput _testOutputHelper; + + /// + /// Implementation of IDisposable. + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + this.Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + /// + /// Code that does the actual disposal of resources. + /// + /// Dispose of resources only if this is true. + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + this._logger.Dispose(); + this._testOutputHelper.Dispose(); + } + } +} diff --git a/dotnet/src/SemanticKernel.IntegrationTests/SemanticKernel.IntegrationTests.csproj b/dotnet/src/SemanticKernel.IntegrationTests/SemanticKernel.IntegrationTests.csproj index 3ab7d55d937e..cef919087215 100644 --- a/dotnet/src/SemanticKernel.IntegrationTests/SemanticKernel.IntegrationTests.csproj +++ b/dotnet/src/SemanticKernel.IntegrationTests/SemanticKernel.IntegrationTests.csproj @@ -26,6 +26,7 @@ + diff --git a/dotnet/src/SemanticKernel.Skills/Skills.FileCompression/FileCompressionSkill.cs b/dotnet/src/SemanticKernel.Skills/Skills.FileCompression/FileCompressionSkill.cs new file mode 100644 index 000000000000..89868572c0c2 --- /dev/null +++ b/dotnet/src/SemanticKernel.Skills/Skills.FileCompression/FileCompressionSkill.cs @@ -0,0 +1,158 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.SemanticKernel.Orchestration; +using Microsoft.SemanticKernel.SkillDefinition; + +namespace Microsoft.SemanticKernel.Skills.FileCompression; + +//********************************************************************************************************************** +// EXAMPLE USAGE +// +// ISemanticKernel kernel = SemanticKernel.Build(); +// var zipCompressor = new ZipFileCompressor(); +// var skill = new FileCompressionSkill(zipCompressor); +// var fileCompression = kernel.ImportSkill(skill, "FileCompression"); +// string sourceFilePath = "FileToCompress.txt"; +// string destinationFilePath = "CompressedFile.zip"; +// var variables = new ContextVariables(sourceFilePath); +// variables.Set(FileCompressionSkill.Parameters.DestinationFilePath, destinationFilePath); +// await kernel.RunAsync(variables, fileCompression["CompressFileAsync"]); +//********************************************************************************************************************** + +/// +/// Skill for compressing and decompressing files. +/// +public class FileCompressionSkill +{ + /// + /// Parameter names. + /// + /// + public static class Parameters + { + /// + /// Directory to which to extract compressed file's data. + /// + public const string DestinationDirectoryPath = "destinationDirectoryPath"; + + /// + /// File path where to save compressed data. + /// + public const string DestinationFilePath = "destinationFilePath"; + } + + private readonly IFileCompressor _fileCompressor; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// File compressor implementation + /// Optional logger + public FileCompressionSkill(IFileCompressor fileCompressor, ILogger? logger = null) + { + this._fileCompressor = fileCompressor ?? throw new ArgumentNullException(nameof(fileCompressor)); + this._logger = logger ?? new NullLogger(); + } + + /// + /// Compresses an input file to an output file. + /// + /// Path of file to compress + /// Semantic Kernel context + /// Path of created compressed file + /// + [SKFunction("Compresses an input file to an output file")] + [SKFunctionInput(Description = "Path of file to compress")] + [SKFunctionContextParameter(Name = Parameters.DestinationFilePath, Description = "Path of compressed file to create")] + public async Task CompressFileAsync(string sourceFilePath, SKContext context) + { + this._logger.LogTrace($"{nameof(CompressFileAsync)} got called"); + + if (!context.Variables.Get(Parameters.DestinationFilePath, out string destinationFilePath)) + { + const string errorMessage = $"Missing context variable {Parameters.DestinationFilePath} in {nameof(CompressFileAsync)}"; + this._logger.LogError(errorMessage); + context.Fail(errorMessage); + + throw new KeyNotFoundException(errorMessage); + } + + await this._fileCompressor.CompressFileAsync(Environment.ExpandEnvironmentVariables(sourceFilePath), + Environment.ExpandEnvironmentVariables(destinationFilePath), + context.CancellationToken); + + return destinationFilePath; + } + + /// + /// Compresses a directory to an output file. + /// + /// Path of directory to compress + /// Semantic Kernel context + /// Path of created compressed file + /// + [SKFunction("Compresses a directory to an output file")] + [SKFunctionInput(Description = "Path of directory to compress")] + [SKFunctionContextParameter(Name = Parameters.DestinationFilePath, Description = "Path of compressed file to create")] + public async Task CompressDirectoryAsync(string sourceDirectoryPath, SKContext context) + { + this._logger.LogTrace($"{nameof(CompressDirectoryAsync)} got called"); + + if (!context.Variables.Get(Parameters.DestinationFilePath, out string destinationFilePath)) + { + const string errorMessage = $"Missing context variable {Parameters.DestinationFilePath} in {nameof(CompressDirectoryAsync)}"; + this._logger.LogError(errorMessage); + context.Fail(errorMessage); + + throw new KeyNotFoundException(errorMessage); + } + + await this._fileCompressor.CompressDirectoryAsync(Environment.ExpandEnvironmentVariables(sourceDirectoryPath), + Environment.ExpandEnvironmentVariables(destinationFilePath), + context.CancellationToken); + + return destinationFilePath; + } + + /// + /// Decompresses an input file. + /// + /// Path of file to decompress + /// Semantic Kernel context + /// Path of created compressed file + /// + [SKFunction("Decompresses an input file")] + [SKFunctionInput(Description = "Path of directory into which decompressed content was extracted")] + [SKFunctionContextParameter(Name = Parameters.DestinationDirectoryPath, Description = "Directory into which to extract the decompressed content")] + public async Task DecompressFileAsync(string sourceFilePath, SKContext context) + { + this._logger.LogTrace($"{nameof(DecompressFileAsync)} got called"); + + if (!context.Variables.Get(Parameters.DestinationDirectoryPath, out string destinationDirectoryPath)) + { + const string errorMessage = $"Missing context variable {Parameters.DestinationDirectoryPath} in {nameof(DecompressFileAsync)}"; + this._logger.LogError(errorMessage); + context.Fail(errorMessage); + + throw new KeyNotFoundException(errorMessage); + } + + if (!Directory.Exists(destinationDirectoryPath)) + { + Directory.CreateDirectory(destinationDirectoryPath); + } + + await this._fileCompressor.DecompressFileAsync(Environment.ExpandEnvironmentVariables(sourceFilePath), + Environment.ExpandEnvironmentVariables(destinationDirectoryPath), + context.CancellationToken); + + return destinationDirectoryPath; + } +} diff --git a/dotnet/src/SemanticKernel.Skills/Skills.FileCompression/IFileCompressor.cs b/dotnet/src/SemanticKernel.Skills/Skills.FileCompression/IFileCompressor.cs new file mode 100644 index 000000000000..75f666f5437f --- /dev/null +++ b/dotnet/src/SemanticKernel.Skills/Skills.FileCompression/IFileCompressor.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.SemanticKernel.Skills.FileCompression; + +/// +/// Interface for file compression / decompression. +/// +public interface IFileCompressor +{ + /// + /// Compress a source file to a destination file. + /// + /// File to compress + /// Compressed file to create + /// Cancellation token + /// Task + public Task CompressFileAsync(string sourceFilePath, string destinationFilePath, CancellationToken cancellationToken); + + /// + /// Compress a source directory to a destination file. + /// + /// Directory to compress + /// Compressed file to create + /// Cancellation token + /// Task + public Task CompressDirectoryAsync(string sourceDirectoryPath, string destinationFilePath, CancellationToken cancellationToken); + + /// + /// Decompress a source file to a destination folder. + /// + /// File to decompress + /// Directory into which to extract the decompressed content + /// Cancellation token + /// Task + public Task DecompressFileAsync(string sourceFilePath, string destinationDirectoryPath, CancellationToken cancellationToken); +} diff --git a/dotnet/src/SemanticKernel.Skills/Skills.FileCompression/Skills.FileCompression.csproj b/dotnet/src/SemanticKernel.Skills/Skills.FileCompression/Skills.FileCompression.csproj new file mode 100644 index 000000000000..a2b91ff4683a --- /dev/null +++ b/dotnet/src/SemanticKernel.Skills/Skills.FileCompression/Skills.FileCompression.csproj @@ -0,0 +1,24 @@ + + + + $([System.IO.Path]::GetDirectoryName($([MSBuild]::GetPathOfFileAbove('.gitignore', '$(MSBuildThisFileDirectory)')))) + + + + + + Microsoft.SemanticKernel.Skills.FileCompression + Microsoft.SemanticKernel.Skills.FileCompression + netstandard2.1 + + + + + Microsoft.SemanticKernel.Skills.FileCompression + Semantic Kernel - File Compression Skill + + + + + + diff --git a/dotnet/src/SemanticKernel.Skills/Skills.FileCompression/ZipFileCompressor.cs b/dotnet/src/SemanticKernel.Skills/Skills.FileCompression/ZipFileCompressor.cs new file mode 100644 index 000000000000..104d42e0809c --- /dev/null +++ b/dotnet/src/SemanticKernel.Skills/Skills.FileCompression/ZipFileCompressor.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.IO; +using System.IO.Compression; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.SemanticKernel.Skills.FileCompression; + +/// +/// Implementation of that uses the Zip format. +/// +public class ZipFileCompressor : IFileCompressor +{ + /// + public Task CompressDirectoryAsync(string sourceDirectoryPath, string destinationFilePath, CancellationToken cancellationToken) + { + return Task.Run(() => ZipFile.CreateFromDirectory(sourceDirectoryPath, destinationFilePath), cancellationToken); + } + + /// + public Task CompressFileAsync(string sourceFilePath, string destinationFilePath, CancellationToken cancellationToken) + { + return Task.Run(() => + { + using (ZipArchive zip = ZipFile.Open(destinationFilePath, ZipArchiveMode.Create)) + { + zip.CreateEntryFromFile(sourceFilePath, Path.GetFileName(sourceFilePath)); + } + }, cancellationToken); + } + + /// + public async Task DecompressFileAsync(string sourceFilePath, string destinationDirectoryPath, CancellationToken cancellationToken) + { + using (ZipArchive archive = await Task.Run(() => ZipFile.OpenRead(sourceFilePath), cancellationToken)) + { + foreach (ZipArchiveEntry entry in archive.Entries) + { + await Task.Run(() => entry.ExtractToFile(Path.Combine(destinationDirectoryPath, entry.FullName)), cancellationToken); + } + } + } +} diff --git a/dotnet/src/SemanticKernel.Skills/Skills.Web/WebFileDownloadSkill.cs b/dotnet/src/SemanticKernel.Skills/Skills.Web/WebFileDownloadSkill.cs index f14b3efa55a6..f653e9dc888a 100644 --- a/dotnet/src/SemanticKernel.Skills/Skills.Web/WebFileDownloadSkill.cs +++ b/dotnet/src/SemanticKernel.Skills/Skills.Web/WebFileDownloadSkill.cs @@ -73,7 +73,7 @@ public async Task DownloadToFileAsync(string source, SKContext context) this._logger.LogDebug("Response received: {0}", response.StatusCode); using Stream webStream = await response.Content.ReadAsStreamAsync(); - using FileStream outputFileStream = new FileStream(filePath, FileMode.Create); + using FileStream outputFileStream = new FileStream(Environment.ExpandEnvironmentVariables(filePath), FileMode.Create); await webStream.CopyToAsync(outputFileStream, context.CancellationToken); } From 64dde1bcc5339d21acbca2ba7d4b6eb807457ee7 Mon Sep 17 00:00:00 2001 From: Gil LaHaye Date: Wed, 15 Mar 2023 21:15:15 -0700 Subject: [PATCH 2/4] Move file compression skill to samples\skills --- dotnet/SK-dotnet.sln | 15 +- .../FileCompression/FileCompressionTests.cs | 140 ------------------ .../SemanticKernel.IntegrationTests.csproj | 1 - .../Skills.FileCompression.csproj | 24 --- .../FileCompression/FileCompression.csproj | 14 ++ .../FileCompression}/FileCompressionSkill.cs | 0 .../FileCompression}/IFileCompressor.cs | 0 .../FileCompression}/ZipFileCompressor.cs | 0 8 files changed, 23 insertions(+), 171 deletions(-) delete mode 100644 dotnet/src/SemanticKernel.IntegrationTests/FileCompression/FileCompressionTests.cs delete mode 100644 dotnet/src/SemanticKernel.Skills/Skills.FileCompression/Skills.FileCompression.csproj create mode 100644 samples/skills/FileCompression/FileCompression.csproj rename {dotnet/src/SemanticKernel.Skills/Skills.FileCompression => samples/skills/FileCompression}/FileCompressionSkill.cs (100%) rename {dotnet/src/SemanticKernel.Skills/Skills.FileCompression => samples/skills/FileCompression}/IFileCompressor.cs (100%) rename {dotnet/src/SemanticKernel.Skills/Skills.FileCompression => samples/skills/FileCompression}/ZipFileCompressor.cs (100%) diff --git a/dotnet/SK-dotnet.sln b/dotnet/SK-dotnet.sln index 481df8505a58..5e0f299882c4 100644 --- a/dotnet/SK-dotnet.sln +++ b/dotnet/SK-dotnet.sln @@ -52,7 +52,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nuget", "nuget", "{F4243136 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KernelBuilder", "..\samples\dotnet\KernelBuilder\KernelBuilder.csproj", "{A52818AC-57FB-495F-818F-9E1E7BC5618C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Skills.FileCompression", "src\SemanticKernel.Skills\Skills.FileCompression\Skills.FileCompression.csproj", "{5D9466B9-6E9F-4337-AC70-1CE8F08385F7}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "skills", "skills", "{DA129631-4D47-4BB6-8109-10FECDA932EC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileCompression", "..\samples\skills\FileCompression\FileCompression.csproj", "{F95F0A28-E756-4DA5-9CC6-F50D996CCF58}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -112,10 +114,10 @@ Global {A52818AC-57FB-495F-818F-9E1E7BC5618C}.Debug|Any CPU.Build.0 = Debug|Any CPU {A52818AC-57FB-495F-818F-9E1E7BC5618C}.Release|Any CPU.ActiveCfg = Release|Any CPU {A52818AC-57FB-495F-818F-9E1E7BC5618C}.Release|Any CPU.Build.0 = Release|Any CPU - {5D9466B9-6E9F-4337-AC70-1CE8F08385F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5D9466B9-6E9F-4337-AC70-1CE8F08385F7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5D9466B9-6E9F-4337-AC70-1CE8F08385F7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5D9466B9-6E9F-4337-AC70-1CE8F08385F7}.Release|Any CPU.Build.0 = Release|Any CPU + {F95F0A28-E756-4DA5-9CC6-F50D996CCF58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F95F0A28-E756-4DA5-9CC6-F50D996CCF58}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F95F0A28-E756-4DA5-9CC6-F50D996CCF58}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F95F0A28-E756-4DA5-9CC6-F50D996CCF58}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -136,7 +138,8 @@ Global {107156B4-5A8B-45C7-97A2-4544D7FA19DE} = {9ECD1AA0-75B3-4E25-B0B5-9F0945B64974} {F4243136-252A-4459-A7C4-EE8C056D6B0B} = {158A4E5E-AEE0-4D60-83C7-8E089B2D881D} {A52818AC-57FB-495F-818F-9E1E7BC5618C} = {FA3720F1-C99A-49B2-9577-A940257098BF} - {5D9466B9-6E9F-4337-AC70-1CE8F08385F7} = {9ECD1AA0-75B3-4E25-B0B5-9F0945B64974} + {DA129631-4D47-4BB6-8109-10FECDA932EC} = {FA3720F1-C99A-49B2-9577-A940257098BF} + {F95F0A28-E756-4DA5-9CC6-F50D996CCF58} = {DA129631-4D47-4BB6-8109-10FECDA932EC} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FBDC56A3-86AD-4323-AA0F-201E59123B83} diff --git a/dotnet/src/SemanticKernel.IntegrationTests/FileCompression/FileCompressionTests.cs b/dotnet/src/SemanticKernel.IntegrationTests/FileCompression/FileCompressionTests.cs deleted file mode 100644 index 097e3a77f2df..000000000000 --- a/dotnet/src/SemanticKernel.IntegrationTests/FileCompression/FileCompressionTests.cs +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.IO; -using System.Threading.Tasks; -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Orchestration; -using Microsoft.SemanticKernel.Skills.FileCompression; -using Microsoft.SemanticKernel.Skills.Web; -using SemanticKernel.IntegrationTests; -using Xunit; -using Xunit.Abstractions; - -namespace IntegrationTests.FileCompression; - -public class FileCompressionTests : IDisposable -{ - public FileCompressionTests(ITestOutputHelper output) - { - this._logger = new XunitLogger(output); - this._output = output; - - this._testOutputHelper = new RedirectOutput(output); - Console.SetOut(this._testOutputHelper); - } - - [Fact] - public async Task ZipFileCompressionAndDecompressionTestAsync() - { - // Arrange - Create objects - IKernel kernel = Kernel.Builder.WithLogger(this._logger).Build(); - using XunitLogger skillLogger = new(this._output); - var zipCompressor = new ZipFileCompressor(); - var skill = new FileCompressionSkill(zipCompressor); - var fileCompression = kernel.ImportSkill(skill, "FileCompression"); - - // Arrange - Create file to compress - string tempPath = Path.GetTempPath(); - string tempFileName = Path.GetRandomFileName(); - var sourceFilePath = Path.Join(tempPath, tempFileName); - await File.WriteAllTextAsync(sourceFilePath, new string('*', 100)); - - var destinationFilePath = sourceFilePath + ".zip"; - var contextVariables = new ContextVariables(sourceFilePath); - contextVariables.Set(FileCompressionSkill.Parameters.DestinationFilePath, destinationFilePath); - - // Act - Compress file and decompress it - await kernel.RunAsync(contextVariables, fileCompression["CompressFileAsync"]); - string uncompressedFilePath = sourceFilePath + ".original"; - File.Move(sourceFilePath, uncompressedFilePath); - contextVariables = new ContextVariables(destinationFilePath); - contextVariables.Set(FileCompressionSkill.Parameters.DestinationDirectoryPath, tempPath); - await kernel.RunAsync(contextVariables, fileCompression["DecompressFileAsync"]); - - // Assert - string uncompressedFileContents = await File.ReadAllTextAsync(uncompressedFilePath); - string decompressedFilePath = sourceFilePath; - string decompressedFileContents = await File.ReadAllTextAsync(uncompressedFilePath); - Assert.Equal(uncompressedFileContents, decompressedFileContents); - - // Clean up - File.Delete(destinationFilePath); // Zip file - File.Delete(uncompressedFilePath); - File.Delete(decompressedFilePath); - } - - [Fact] - public async Task ZipDirectoryCompressionAndDecompressionTestAsync() - { - // Arrange - Create objects - const string File1 = "file1.txt"; - const string File2 = "file2.txt"; - IKernel kernel = Kernel.Builder.WithLogger(this._logger).Build(); - using XunitLogger skillLogger = new(this._output); - var zipCompressor = new ZipFileCompressor(); - var skill = new FileCompressionSkill(zipCompressor); - var fileCompression = kernel.ImportSkill(skill, "FileCompression"); - - // Arrange - Create a folder with 2 files to compress - string tempPath = Path.GetTempPath(); - string tempSubDirectoryName = Path.GetRandomFileName(); - string directoryToCompress = Path.Join(tempPath, tempSubDirectoryName); - Directory.CreateDirectory(directoryToCompress); - var sourceFilePath1 = Path.Join(directoryToCompress, File1); - await File.WriteAllTextAsync(sourceFilePath1, new string('*', 100)); - var sourceFilePath2 = Path.Join(directoryToCompress, File2); - await File.WriteAllTextAsync(sourceFilePath2, new string('*', 200)); - var destinationFilePath = Path.Join(tempPath, tempSubDirectoryName + ".zip"); - - var contextVariables = new ContextVariables(directoryToCompress); - contextVariables.Set(FileCompressionSkill.Parameters.DestinationFilePath, destinationFilePath); - - // Act - Compress folder and decompress it - await kernel.RunAsync(contextVariables, fileCompression["CompressDirectoryAsync"]); - string decompressedFilesDirectory = Path.Join(tempPath, tempSubDirectoryName + "decompressed"); - contextVariables = new ContextVariables(destinationFilePath); - contextVariables.Set(FileCompressionSkill.Parameters.DestinationDirectoryPath, decompressedFilesDirectory); - await kernel.RunAsync(contextVariables, fileCompression["DecompressFileAsync"]); - - // Assert - string uncompressedFile1Contents = await File.ReadAllTextAsync(sourceFilePath1); - string uncompressedFile2Contents = await File.ReadAllTextAsync(sourceFilePath2); - string decompressedFile1Contents = await File.ReadAllTextAsync(Path.Join(decompressedFilesDirectory, File1)); - string decompressedFile2Contents = await File.ReadAllTextAsync(Path.Join(decompressedFilesDirectory, File2)); - Assert.Equal(uncompressedFile1Contents, decompressedFile1Contents); - Assert.Equal(uncompressedFile2Contents, decompressedFile2Contents); - - // Cleanup - File.Delete(destinationFilePath); // Zip file - Directory.Delete(directoryToCompress, recursive: true); - Directory.Delete(decompressedFilesDirectory, recursive: true); - } - - private readonly XunitLogger _logger; - private readonly ITestOutputHelper _output; - private readonly RedirectOutput _testOutputHelper; - - /// - /// Implementation of IDisposable. - /// - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - this.Dispose(disposing: true); - GC.SuppressFinalize(this); - } - - /// - /// Code that does the actual disposal of resources. - /// - /// Dispose of resources only if this is true. - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - this._logger.Dispose(); - this._testOutputHelper.Dispose(); - } - } -} diff --git a/dotnet/src/SemanticKernel.IntegrationTests/SemanticKernel.IntegrationTests.csproj b/dotnet/src/SemanticKernel.IntegrationTests/SemanticKernel.IntegrationTests.csproj index cef919087215..3ab7d55d937e 100644 --- a/dotnet/src/SemanticKernel.IntegrationTests/SemanticKernel.IntegrationTests.csproj +++ b/dotnet/src/SemanticKernel.IntegrationTests/SemanticKernel.IntegrationTests.csproj @@ -26,7 +26,6 @@ - diff --git a/dotnet/src/SemanticKernel.Skills/Skills.FileCompression/Skills.FileCompression.csproj b/dotnet/src/SemanticKernel.Skills/Skills.FileCompression/Skills.FileCompression.csproj deleted file mode 100644 index a2b91ff4683a..000000000000 --- a/dotnet/src/SemanticKernel.Skills/Skills.FileCompression/Skills.FileCompression.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - - $([System.IO.Path]::GetDirectoryName($([MSBuild]::GetPathOfFileAbove('.gitignore', '$(MSBuildThisFileDirectory)')))) - - - - - - Microsoft.SemanticKernel.Skills.FileCompression - Microsoft.SemanticKernel.Skills.FileCompression - netstandard2.1 - - - - - Microsoft.SemanticKernel.Skills.FileCompression - Semantic Kernel - File Compression Skill - - - - - - diff --git a/samples/skills/FileCompression/FileCompression.csproj b/samples/skills/FileCompression/FileCompression.csproj new file mode 100644 index 000000000000..f9d4f4161133 --- /dev/null +++ b/samples/skills/FileCompression/FileCompression.csproj @@ -0,0 +1,14 @@ + + + + netstandard2.1 + enable + false + 10.0 + + + + + + + diff --git a/dotnet/src/SemanticKernel.Skills/Skills.FileCompression/FileCompressionSkill.cs b/samples/skills/FileCompression/FileCompressionSkill.cs similarity index 100% rename from dotnet/src/SemanticKernel.Skills/Skills.FileCompression/FileCompressionSkill.cs rename to samples/skills/FileCompression/FileCompressionSkill.cs diff --git a/dotnet/src/SemanticKernel.Skills/Skills.FileCompression/IFileCompressor.cs b/samples/skills/FileCompression/IFileCompressor.cs similarity index 100% rename from dotnet/src/SemanticKernel.Skills/Skills.FileCompression/IFileCompressor.cs rename to samples/skills/FileCompression/IFileCompressor.cs diff --git a/dotnet/src/SemanticKernel.Skills/Skills.FileCompression/ZipFileCompressor.cs b/samples/skills/FileCompression/ZipFileCompressor.cs similarity index 100% rename from dotnet/src/SemanticKernel.Skills/Skills.FileCompression/ZipFileCompressor.cs rename to samples/skills/FileCompression/ZipFileCompressor.cs From 0fff5e3e42ef9ca1f8827bdcbf62cd78fce28ed4 Mon Sep 17 00:00:00 2001 From: Gil LaHaye Date: Thu, 16 Mar 2023 14:39:46 -0700 Subject: [PATCH 3/4] Move FileCompression to samples/dotnet --- dotnet/SK-dotnet.sln | 15 ++++++--------- .../FileCompression/FileCompression.csproj | 0 .../FileCompression/FileCompressionSkill.cs | 0 .../FileCompression/IFileCompressor.cs | 0 .../FileCompression/ZipFileCompressor.cs | 0 5 files changed, 6 insertions(+), 9 deletions(-) rename samples/{skills => dotnet}/FileCompression/FileCompression.csproj (100%) rename samples/{skills => dotnet}/FileCompression/FileCompressionSkill.cs (100%) rename samples/{skills => dotnet}/FileCompression/IFileCompressor.cs (100%) rename samples/{skills => dotnet}/FileCompression/ZipFileCompressor.cs (100%) diff --git a/dotnet/SK-dotnet.sln b/dotnet/SK-dotnet.sln index 5e0f299882c4..b0ad525202c5 100644 --- a/dotnet/SK-dotnet.sln +++ b/dotnet/SK-dotnet.sln @@ -52,9 +52,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nuget", "nuget", "{F4243136 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KernelBuilder", "..\samples\dotnet\KernelBuilder\KernelBuilder.csproj", "{A52818AC-57FB-495F-818F-9E1E7BC5618C}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "skills", "skills", "{DA129631-4D47-4BB6-8109-10FECDA932EC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileCompression", "..\samples\skills\FileCompression\FileCompression.csproj", "{F95F0A28-E756-4DA5-9CC6-F50D996CCF58}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileCompression", "..\samples\dotnet\FileCompression\FileCompression.csproj", "{BC70A5D8-2125-4C37-8C0E-C903EAFA9772}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -114,10 +112,10 @@ Global {A52818AC-57FB-495F-818F-9E1E7BC5618C}.Debug|Any CPU.Build.0 = Debug|Any CPU {A52818AC-57FB-495F-818F-9E1E7BC5618C}.Release|Any CPU.ActiveCfg = Release|Any CPU {A52818AC-57FB-495F-818F-9E1E7BC5618C}.Release|Any CPU.Build.0 = Release|Any CPU - {F95F0A28-E756-4DA5-9CC6-F50D996CCF58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F95F0A28-E756-4DA5-9CC6-F50D996CCF58}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F95F0A28-E756-4DA5-9CC6-F50D996CCF58}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F95F0A28-E756-4DA5-9CC6-F50D996CCF58}.Release|Any CPU.Build.0 = Release|Any CPU + {BC70A5D8-2125-4C37-8C0E-C903EAFA9772}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BC70A5D8-2125-4C37-8C0E-C903EAFA9772}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BC70A5D8-2125-4C37-8C0E-C903EAFA9772}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BC70A5D8-2125-4C37-8C0E-C903EAFA9772}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -138,8 +136,7 @@ Global {107156B4-5A8B-45C7-97A2-4544D7FA19DE} = {9ECD1AA0-75B3-4E25-B0B5-9F0945B64974} {F4243136-252A-4459-A7C4-EE8C056D6B0B} = {158A4E5E-AEE0-4D60-83C7-8E089B2D881D} {A52818AC-57FB-495F-818F-9E1E7BC5618C} = {FA3720F1-C99A-49B2-9577-A940257098BF} - {DA129631-4D47-4BB6-8109-10FECDA932EC} = {FA3720F1-C99A-49B2-9577-A940257098BF} - {F95F0A28-E756-4DA5-9CC6-F50D996CCF58} = {DA129631-4D47-4BB6-8109-10FECDA932EC} + {BC70A5D8-2125-4C37-8C0E-C903EAFA9772} = {FA3720F1-C99A-49B2-9577-A940257098BF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FBDC56A3-86AD-4323-AA0F-201E59123B83} diff --git a/samples/skills/FileCompression/FileCompression.csproj b/samples/dotnet/FileCompression/FileCompression.csproj similarity index 100% rename from samples/skills/FileCompression/FileCompression.csproj rename to samples/dotnet/FileCompression/FileCompression.csproj diff --git a/samples/skills/FileCompression/FileCompressionSkill.cs b/samples/dotnet/FileCompression/FileCompressionSkill.cs similarity index 100% rename from samples/skills/FileCompression/FileCompressionSkill.cs rename to samples/dotnet/FileCompression/FileCompressionSkill.cs diff --git a/samples/skills/FileCompression/IFileCompressor.cs b/samples/dotnet/FileCompression/IFileCompressor.cs similarity index 100% rename from samples/skills/FileCompression/IFileCompressor.cs rename to samples/dotnet/FileCompression/IFileCompressor.cs diff --git a/samples/skills/FileCompression/ZipFileCompressor.cs b/samples/dotnet/FileCompression/ZipFileCompressor.cs similarity index 100% rename from samples/skills/FileCompression/ZipFileCompressor.cs rename to samples/dotnet/FileCompression/ZipFileCompressor.cs From eeda56aa93281c8cc17d1219f07e0db67692e649 Mon Sep 17 00:00:00 2001 From: Gil LaHaye Date: Mon, 27 Mar 2023 16:10:39 -0700 Subject: [PATCH 4/4] Fix review comments from Adrian --- .../Skills.Web/WebFileDownloadSkill.cs | 2 +- .../FileCompression/FileCompressionSkill.cs | 43 +++++++++---------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/dotnet/src/SemanticKernel.Skills/Skills.Web/WebFileDownloadSkill.cs b/dotnet/src/SemanticKernel.Skills/Skills.Web/WebFileDownloadSkill.cs index f653e9dc888a..de9dbcce6444 100644 --- a/dotnet/src/SemanticKernel.Skills/Skills.Web/WebFileDownloadSkill.cs +++ b/dotnet/src/SemanticKernel.Skills/Skills.Web/WebFileDownloadSkill.cs @@ -64,7 +64,7 @@ public async Task DownloadToFileAsync(string source, SKContext context) string errorMessage = $"Missing variable {Parameters.FilePath}"; context.Fail(errorMessage); - throw new KeyNotFoundException(errorMessage); + return; } this._logger.LogDebug("Sending GET request for {0}", source); diff --git a/samples/dotnet/FileCompression/FileCompressionSkill.cs b/samples/dotnet/FileCompression/FileCompressionSkill.cs index 89868572c0c2..d4b7489dafd4 100644 --- a/samples/dotnet/FileCompression/FileCompressionSkill.cs +++ b/samples/dotnet/FileCompression/FileCompressionSkill.cs @@ -11,23 +11,20 @@ namespace Microsoft.SemanticKernel.Skills.FileCompression; -//********************************************************************************************************************** -// EXAMPLE USAGE -// -// ISemanticKernel kernel = SemanticKernel.Build(); -// var zipCompressor = new ZipFileCompressor(); -// var skill = new FileCompressionSkill(zipCompressor); -// var fileCompression = kernel.ImportSkill(skill, "FileCompression"); -// string sourceFilePath = "FileToCompress.txt"; -// string destinationFilePath = "CompressedFile.zip"; -// var variables = new ContextVariables(sourceFilePath); -// variables.Set(FileCompressionSkill.Parameters.DestinationFilePath, destinationFilePath); -// await kernel.RunAsync(variables, fileCompression["CompressFileAsync"]); -//********************************************************************************************************************** - /// /// Skill for compressing and decompressing files. /// +/// +/// ISemanticKernel kernel = SemanticKernel.Build(); +/// var zipCompressor = new ZipFileCompressor(); +/// var skill = new FileCompressionSkill(zipCompressor); +/// var fileCompression = kernel.ImportSkill(skill, "FileCompression"); +/// string sourceFilePath = "FileToCompress.txt"; +/// string destinationFilePath = "CompressedFile.zip"; +/// var variables = new ContextVariables(sourceFilePath); +/// variables.Set(FileCompressionSkill.Parameters.DestinationFilePath, destinationFilePath); +/// await kernel.RunAsync(variables, fileCompression["CompressFileAsync"]); +/// public class FileCompressionSkill { /// @@ -47,9 +44,6 @@ public static class Parameters public const string DestinationFilePath = "destinationFilePath"; } - private readonly IFileCompressor _fileCompressor; - private readonly ILogger _logger; - /// /// Initializes a new instance of the class. /// @@ -71,7 +65,7 @@ public FileCompressionSkill(IFileCompressor fileCompressor, ILogger CompressFileAsync(string sourceFilePath, SKContext context) + public async Task CompressFileAsync(string sourceFilePath, SKContext context) { this._logger.LogTrace($"{nameof(CompressFileAsync)} got called"); @@ -81,7 +75,7 @@ public async Task CompressFileAsync(string sourceFilePath, SKContext con this._logger.LogError(errorMessage); context.Fail(errorMessage); - throw new KeyNotFoundException(errorMessage); + return null; } await this._fileCompressor.CompressFileAsync(Environment.ExpandEnvironmentVariables(sourceFilePath), @@ -101,7 +95,7 @@ await this._fileCompressor.CompressFileAsync(Environment.ExpandEnvironmentVariab [SKFunction("Compresses a directory to an output file")] [SKFunctionInput(Description = "Path of directory to compress")] [SKFunctionContextParameter(Name = Parameters.DestinationFilePath, Description = "Path of compressed file to create")] - public async Task CompressDirectoryAsync(string sourceDirectoryPath, SKContext context) + public async Task CompressDirectoryAsync(string sourceDirectoryPath, SKContext context) { this._logger.LogTrace($"{nameof(CompressDirectoryAsync)} got called"); @@ -111,7 +105,7 @@ public async Task CompressDirectoryAsync(string sourceDirectoryPath, SKC this._logger.LogError(errorMessage); context.Fail(errorMessage); - throw new KeyNotFoundException(errorMessage); + return null; } await this._fileCompressor.CompressDirectoryAsync(Environment.ExpandEnvironmentVariables(sourceDirectoryPath), @@ -131,7 +125,7 @@ await this._fileCompressor.CompressDirectoryAsync(Environment.ExpandEnvironmentV [SKFunction("Decompresses an input file")] [SKFunctionInput(Description = "Path of directory into which decompressed content was extracted")] [SKFunctionContextParameter(Name = Parameters.DestinationDirectoryPath, Description = "Directory into which to extract the decompressed content")] - public async Task DecompressFileAsync(string sourceFilePath, SKContext context) + public async Task DecompressFileAsync(string sourceFilePath, SKContext context) { this._logger.LogTrace($"{nameof(DecompressFileAsync)} got called"); @@ -141,7 +135,7 @@ public async Task DecompressFileAsync(string sourceFilePath, SKContext c this._logger.LogError(errorMessage); context.Fail(errorMessage); - throw new KeyNotFoundException(errorMessage); + return null; } if (!Directory.Exists(destinationDirectoryPath)) @@ -155,4 +149,7 @@ await this._fileCompressor.DecompressFileAsync(Environment.ExpandEnvironmentVari return destinationDirectoryPath; } + + private readonly IFileCompressor _fileCompressor; + private readonly ILogger _logger; }