diff --git a/LLama.Examples/Assets/sample-KM-Readme.pdf b/LLama.Examples/Assets/sample-KM-Readme.pdf new file mode 100644 index 00000000..0de3ce82 Binary files /dev/null and b/LLama.Examples/Assets/sample-KM-Readme.pdf differ diff --git a/LLama.Examples/ExampleRunner.cs b/LLama.Examples/ExampleRunner.cs index 869d1edf..b3a52d4c 100644 --- a/LLama.Examples/ExampleRunner.cs +++ b/LLama.Examples/ExampleRunner.cs @@ -5,26 +5,27 @@ public class ExampleRunner { private static readonly Dictionary> Examples = new() { - { "Run a chat session with history.", ChatSessionWithHistory.Run }, - { "Run a chat session without stripping the role names.", ChatSessionWithRoleName.Run }, - { "Run a chat session with the role names stripped.", ChatSessionStripRoleName.Run }, - { "Run a chat session in Chinese GB2312 encoding", ChatChineseGB2312.Run }, - { "Interactive mode chat by using executor.", InteractiveModeExecute.Run }, - { "Instruct mode chat by using executor.", InstructModeExecute.Run }, - { "Stateless mode chat by using executor.", StatelessModeExecute.Run }, - { "Load and save chat session.", SaveAndLoadSession.Run }, - { "Load and save state of model and executor.", LoadAndSaveState.Run }, - { "Get embeddings from LLama model.", () => Task.Run(GetEmbeddings.Run) }, - { "Quantize the model.", () => Task.Run(QuantizeModel.Run) }, - { "Automatic conversation.", TalkToYourself.Run }, - { "Constrain response to json format using grammar.", GrammarJsonResponse.Run }, - { "Semantic Kernel Prompt.", SemanticKernelPrompt.Run }, - { "Semantic Kernel Chat.", SemanticKernelChat.Run }, - { "Semantic Kernel Memory.", SemanticKernelMemory.Run }, - { "Coding Assistant.", CodingAssistant.Run }, - { "Batched Executor (Fork)", BatchedExecutorFork.Run }, - { "Batched Executor (Rewind)", BatchedExecutorRewind.Run }, - { "SK Kernel Memory.", KernelMemory.Run }, + { "Chat Session: History", ChatSessionWithHistory.Run }, + { "Chat Session: Role names", ChatSessionWithRoleName.Run }, + { "Chat Session: Role names stripped", ChatSessionStripRoleName.Run }, + { "Chat Session: Coding Assistant", CodingAssistant.Run }, + { "Chat Session: Automatic conversation", TalkToYourself.Run }, + { "Chat Session: Chinese characters", ChatChineseGB2312.Run }, + { "Executor: Interactive mode chat", InteractiveModeExecute.Run }, + { "Executor: Instruct mode chat", InstructModeExecute.Run }, + { "Executor: Stateless mode chat", StatelessModeExecute.Run }, + { "Save and Load: chat session", SaveAndLoadSession.Run }, + { "Save and Load: state of model and executor", LoadAndSaveState.Run }, + { "LLama Model: Get embeddings", () => Task.Run(GetEmbeddings.Run) }, + { "LLama Model: Quantize", () => Task.Run(QuantizeModel.Run) }, + { "Grammar: Constrain response to json format", GrammarJsonResponse.Run }, + { "Kernel Memory: Document Q&A", KernelMemory.Run }, + { "Kernel Memory: Save and Load", KernelMemorySaveAndLoad.Run }, + { "Semantic Kernel: Prompt", SemanticKernelPrompt.Run }, + { "Semantic Kernel: Chat", SemanticKernelChat.Run }, + { "Semantic Kernel: Store", SemanticKernelMemory.Run }, + { "Batched Executor: Fork", BatchedExecutorFork.Run }, + { "Batched Executor: Rewind", BatchedExecutorRewind.Run }, { "Exit", () => { Environment.Exit(0); return Task.CompletedTask; } } }; @@ -44,8 +45,10 @@ public class ExampleRunner AnsiConsole.Write(new Rule(choice)); await example(); } - Console.WriteLine("Press any key to continue..."); - Console.ReadKey(); + + Console.WriteLine("Press ENTER to go to the main menu..."); + Console.ReadLine(); + AnsiConsole.Clear(); } } diff --git a/LLama.Examples/Examples/KernelMemory.cs b/LLama.Examples/Examples/KernelMemory.cs index df250af7..6258a10b 100644 --- a/LLama.Examples/Examples/KernelMemory.cs +++ b/LLama.Examples/Examples/KernelMemory.cs @@ -1,58 +1,110 @@ using LLamaSharp.KernelMemory; using Microsoft.KernelMemory; using Microsoft.KernelMemory.Configuration; +using System.Diagnostics; namespace LLama.Examples.Examples { + // This example is from Microsoft's official kernel memory "custom prompts" example: + // https://github.com/microsoft/kernel-memory/blob/6d516d70a23d50c6cb982e822e6a3a9b2e899cfa/examples/101-dotnet-custom-Prompts/Program.cs#L1-L86 + + // Microsoft.KernelMemory has more features than Microsoft.SemanticKernel. + // See https://microsoft.github.io/kernel-memory/ for details. + public class KernelMemory { public static async Task Run() { + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine( + """ + + This program uses the Microsoft.KernelMemory package to ingest documents + and answer questions about them in an interactive chat prompt. + + """); + + // Setup the kernel memory with the LLM model string modelPath = UserSettings.GetModelPath(); + IKernelMemory memory = CreateMemory(modelPath); - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine("This example is from : \n" + - "https://github.com/microsoft/kernel-memory/blob/main/examples/101-using-core-nuget/Program.cs"); + // Ingest documents (format is automatically detected from the filename) + string[] filesToIngest = [ + Path.GetFullPath(@"./Assets/sample-SK-Readme.pdf"), + Path.GetFullPath(@"./Assets/sample-KM-Readme.pdf"), + ]; - var searchClientConfig = new SearchClientConfig + for (int i = 0; i < filesToIngest.Length; i++) { - MaxMatchesCount = 1, - AnswerTokens = 100, - }; + string path = filesToIngest[i]; + Stopwatch sw = Stopwatch.StartNew(); + Console.ForegroundColor = ConsoleColor.Blue; + Console.WriteLine($"Importing {i + 1} of {filesToIngest.Length}: {path}"); + await memory.ImportDocumentAsync(path, steps: Constants.PipelineWithoutSummary); + Console.WriteLine($"Completed in {sw.Elapsed}\n"); + } - var memory = new KernelMemoryBuilder() - .WithLLamaSharpDefaults(new LLamaSharpConfig(modelPath) - { - DefaultInferenceParams = new Common.InferenceParams - { - AntiPrompts = new List { "\n\n" } - } - }) - .WithSearchClientConfig(searchClientConfig) - .With(new TextPartitioningOptions - { - MaxTokensPerParagraph = 300, - MaxTokensPerLine = 100, - OverlappingTokens = 30 - }) - .Build(); + // Ask a predefined question + Console.ForegroundColor = ConsoleColor.Green; + string question1 = "What formats does KM support"; + Console.WriteLine($"Question: {question1}"); + await AnswerQuestion(memory, question1); + + // Let the user ask additional questions + while (true) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.Write("Question: "); + string question = Console.ReadLine()!; + if (string.IsNullOrEmpty(question)) + return; - await memory.ImportDocumentAsync(@"./Assets/sample-SK-Readme.pdf", steps: Constants.PipelineWithoutSummary); + await AnswerQuestion(memory, question); + } + } + + private static IKernelMemory CreateMemory(string modelPath) + { + Common.InferenceParams infParams = new() { AntiPrompts = ["\n\n"] }; - var question = "What's Semantic Kernel?"; + LLamaSharpConfig lsConfig = new(modelPath) { DefaultInferenceParams = infParams }; + + SearchClientConfig searchClientConfig = new() + { + MaxMatchesCount = 1, + AnswerTokens = 100, + }; - Console.WriteLine($"\n\nQuestion: {question}"); + TextPartitioningOptions parseOptions = new() + { + MaxTokensPerParagraph = 300, + MaxTokensPerLine = 100, + OverlappingTokens = 30 + }; - var answer = await memory.AskAsync(question); + return new KernelMemoryBuilder() + .WithLLamaSharpDefaults(lsConfig) + .WithSearchClientConfig(searchClientConfig) + .With(parseOptions) + .Build(); + } - Console.WriteLine($"\nAnswer: {answer.Result}"); + private static async Task AnswerQuestion(IKernelMemory memory, string question) + { + Stopwatch sw = Stopwatch.StartNew(); + Console.ForegroundColor = ConsoleColor.DarkGray; + Console.WriteLine($"Generating answer..."); - Console.WriteLine("\n\n Sources:\n"); + MemoryAnswer answer = await memory.AskAsync(question); + Console.WriteLine($"Answer generated in {sw.Elapsed}"); - foreach (var x in answer.RelevantSources) + Console.ForegroundColor = ConsoleColor.Gray; + Console.WriteLine($"Answer: {answer.Result}"); + foreach (var source in answer.RelevantSources) { - Console.WriteLine($" - {x.SourceName} - {x.Link} [{x.Partitions.First().LastUpdate:D}]"); + Console.WriteLine($"Source: {source.SourceName}"); } + Console.WriteLine(); } } -} +} \ No newline at end of file diff --git a/LLama.Examples/Examples/KernelMemorySaveAndLoad.cs b/LLama.Examples/Examples/KernelMemorySaveAndLoad.cs new file mode 100644 index 00000000..9f0b6ce4 --- /dev/null +++ b/LLama.Examples/Examples/KernelMemorySaveAndLoad.cs @@ -0,0 +1,161 @@ +using LLamaSharp.KernelMemory; +using Microsoft.KernelMemory; +using Microsoft.KernelMemory.Configuration; +using Microsoft.KernelMemory.ContentStorage.DevTools; +using Microsoft.KernelMemory.FileSystem.DevTools; +using Microsoft.KernelMemory.MemoryStorage.DevTools; +using System.Diagnostics; + +namespace LLama.Examples.Examples; + +public class KernelMemorySaveAndLoad +{ + static string StorageFolder => Path.GetFullPath($"./storage-{nameof(KernelMemorySaveAndLoad)}"); + static bool StorageExists => Directory.Exists(StorageFolder) && Directory.GetDirectories(StorageFolder).Length > 0; + + public static async Task Run() + { + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine( + """ + + This program uses the Microsoft.KernelMemory package to ingest documents + and store the embeddings as local files so they can be quickly recalled + when this application is launched again. + + """); + + string modelPath = UserSettings.GetModelPath(); + IKernelMemory memory = CreateMemoryWithLocalStorage(modelPath); + + Console.ForegroundColor = ConsoleColor.Yellow; + if (StorageExists) + { + Console.WriteLine( + """ + + Kernel memory files have been located! + Information about previously analyzed documents has been loaded. + + """); + } + else + { + Console.WriteLine( + $""" + + Existing kernel memory was not found. + Documents will be analyzed (slow) and information saved to disk. + Analysis will not be required the next time this program is run. + Press ENTER to proceed... + + """); + Console.ReadLine(); + await IngestDocuments(memory); + } + + await AskSingleQuestion(memory, "What formats does KM support?"); + await StartUserChatSession(memory); + } + + private static IKernelMemory CreateMemoryWithLocalStorage(string modelPath) + { + Common.InferenceParams infParams = new() { AntiPrompts = ["\n\n"] }; + + LLamaSharpConfig lsConfig = new(modelPath) { DefaultInferenceParams = infParams }; + + SearchClientConfig searchClientConfig = new() + { + MaxMatchesCount = 1, + AnswerTokens = 100, + }; + + TextPartitioningOptions parseOptions = new() + { + MaxTokensPerParagraph = 300, + MaxTokensPerLine = 100, + OverlappingTokens = 30 + }; + + SimpleFileStorageConfig storageConfig = new() + { + Directory = StorageFolder, + StorageType = FileSystemTypes.Disk, + }; + + SimpleVectorDbConfig vectorDbConfig = new() + { + Directory = StorageFolder, + StorageType = FileSystemTypes.Disk, + }; + + Console.ForegroundColor = ConsoleColor.Blue; + Console.WriteLine($"Kernel memory folder: {StorageFolder}"); + + Console.ForegroundColor = ConsoleColor.DarkGray; + return new KernelMemoryBuilder() + .WithSimpleFileStorage(storageConfig) + .WithSimpleVectorDb(vectorDbConfig) + .WithLLamaSharpDefaults(lsConfig) + .WithSearchClientConfig(searchClientConfig) + .With(parseOptions) + .Build(); + } + + private static async Task AskSingleQuestion(IKernelMemory memory, string question) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"Question: {question}"); + await ShowAnswer(memory, question); + } + + private static async Task StartUserChatSession(IKernelMemory memory) + { + while (true) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.Write("Question: "); + string question = Console.ReadLine()!; + if (string.IsNullOrEmpty(question)) + return; + + await ShowAnswer(memory, question); + } + } + + private static async Task IngestDocuments(IKernelMemory memory) + { + string[] filesToIngest = [ + Path.GetFullPath(@"./Assets/sample-SK-Readme.pdf"), + Path.GetFullPath(@"./Assets/sample-KM-Readme.pdf"), + ]; + + for (int i = 0; i < filesToIngest.Length; i++) + { + string path = filesToIngest[i]; + Stopwatch sw = Stopwatch.StartNew(); + Console.ForegroundColor = ConsoleColor.Blue; + Console.WriteLine($"Importing {i + 1} of {filesToIngest.Length}: {path}"); + await memory.ImportDocumentAsync(path, steps: Constants.PipelineWithoutSummary); + Console.WriteLine($"Completed in {sw.Elapsed}\n"); + } + } + + private static async Task ShowAnswer(IKernelMemory memory, string question) + { + Stopwatch sw = Stopwatch.StartNew(); + Console.ForegroundColor = ConsoleColor.DarkGray; + Console.WriteLine($"Generating answer..."); + + MemoryAnswer answer = await memory.AskAsync(question); + Console.WriteLine($"Answer generated in {sw.Elapsed}"); + + Console.ForegroundColor = ConsoleColor.Gray; + Console.WriteLine($"Answer: {answer.Result}"); + foreach (var source in answer.RelevantSources) + { + Console.WriteLine($"Source: {source.SourceName}"); + } + Console.WriteLine(); + } +} \ No newline at end of file diff --git a/LLama.Examples/LLama.Examples.csproj b/LLama.Examples/LLama.Examples.csproj index cf516771..602a5927 100644 --- a/LLama.Examples/LLama.Examples.csproj +++ b/LLama.Examples/LLama.Examples.csproj @@ -58,6 +58,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest