From 7d263900c003b64b6b493a6e10ae31e10527c98d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20W=C3=A4scher?= Date: Fri, 23 Aug 2024 15:16:34 +0200 Subject: [PATCH] Improve console demos --- OllamaApiConsole/Demos/ChatConsole.cs | 48 +++++-- OllamaApiConsole/Demos/ImageChatConsole.cs | 138 ++++++++++++--------- OllamaApiConsole/Demos/ToolConsole.cs | 80 +++++++----- OllamaApiConsole/OllamaConsole.cs | 13 +- 4 files changed, 174 insertions(+), 105 deletions(-) diff --git a/OllamaApiConsole/Demos/ChatConsole.cs b/OllamaApiConsole/Demos/ChatConsole.cs index 040078d..52351f5 100644 --- a/OllamaApiConsole/Demos/ChatConsole.cs +++ b/OllamaApiConsole/Demos/ChatConsole.cs @@ -1,4 +1,5 @@ using OllamaSharp; +using OllamaSharp.Models.Chat; using Spectre.Console; public class ChatConsole : OllamaConsole @@ -15,26 +16,49 @@ public override async Task Run() Ollama.SelectedModel = await SelectModel("Select a model you want to chat with:"); + if (!string.IsNullOrEmpty(Ollama.SelectedModel)) { - AnsiConsole.MarkupLine($"You are talking to [blue]{Ollama.SelectedModel}[/] now."); - AnsiConsole.MarkupLine("[gray]Type \"[red]exit[/]\" to leave the chat.[/]"); - - var chat = Ollama.Chat(stream => AnsiConsole.MarkupInterpolated($"[cyan]{stream?.Message.Content ?? ""}[/]")); - string message; + var keepChatting = true; + var systemPrompt = ReadMultilineInput("Define a system prompt (optional)"); do { - AnsiConsole.WriteLine(); - message = ReadMultilineInput(); + AnsiConsole.MarkupLine(""); + AnsiConsole.MarkupLine($"You are talking to [blue]{Ollama.SelectedModel}[/] now."); + AnsiConsole.MarkupLine("[gray]Submit your messages by hitting return twice.[/]"); + AnsiConsole.MarkupLine("[gray]Type \"[red]/new[/]\" to start over.[/]"); + AnsiConsole.MarkupLine("[gray]Type \"[red]/exit[/]\" to leave the chat.[/]"); + + var chat = Ollama.Chat(stream => AnsiConsole.MarkupInterpolated($"[cyan]{stream?.Message.Content ?? ""}[/]")); + + if (!string.IsNullOrEmpty(systemPrompt)) + chat.SetMessages([new Message { Role = ChatRole.System, Content = systemPrompt }]); + + string message; + + do + { + AnsiConsole.WriteLine(); + message = ReadMultilineInput(); + + if (message.Equals("/exit", StringComparison.OrdinalIgnoreCase)) + { + keepChatting = false; + break; + } - if (message.Equals("exit", StringComparison.OrdinalIgnoreCase)) - break; + if (message.Equals("/new", StringComparison.OrdinalIgnoreCase)) + { + keepChatting = true; + break; + } - await chat.Send(message); + await chat.Send(message); - AnsiConsole.WriteLine(); - } while (!string.IsNullOrEmpty(message)); + AnsiConsole.WriteLine(); + } while (!string.IsNullOrEmpty(message)); + } while (keepChatting); } } } \ No newline at end of file diff --git a/OllamaApiConsole/Demos/ImageChatConsole.cs b/OllamaApiConsole/Demos/ImageChatConsole.cs index 0814d3e..0d346cf 100644 --- a/OllamaApiConsole/Demos/ImageChatConsole.cs +++ b/OllamaApiConsole/Demos/ImageChatConsole.cs @@ -1,5 +1,6 @@ using System.Text.RegularExpressions; using OllamaSharp; +using OllamaSharp.Models.Chat; using Spectre.Console; public class ImageChatConsole : OllamaConsole @@ -16,84 +17,107 @@ public override async Task Run() Ollama.SelectedModel = await SelectModel("Select a model you want to chat with:"); + if (!string.IsNullOrEmpty(Ollama.SelectedModel)) { - AnsiConsole.MarkupLine($"You are talking to [blue]{Ollama.SelectedModel}[/] now."); - AnsiConsole.MarkupLine("[gray]Type \"[red]exit[/]\" to leave the chat.[/]"); - AnsiConsole.MarkupLine("[gray]To send an image, enter its filename in curly braces,[/]"); - AnsiConsole.MarkupLine("[gray]like this {c:/image.jpg}[/]"); - - var chat = Ollama.Chat(stream => AnsiConsole.MarkupInterpolated($"[cyan]{stream?.Message.Content ?? ""}[/]")); - string message; + var keepChatting = true; + var systemPrompt = ReadMultilineInput("Define a system prompt (optional)"); do { - AnsiConsole.WriteLine(); - message = ReadInput(); + AnsiConsole.MarkupLine(""); + AnsiConsole.MarkupLine($"You are talking to [blue]{Ollama.SelectedModel}[/] now."); + AnsiConsole.MarkupLine("[gray]To send an image, enter its filename in curly braces,[/]"); + AnsiConsole.MarkupLine("[gray]like this {c:/image.jpg}[/]"); + AnsiConsole.MarkupLine("[gray]Submit your messages by hitting return twice.[/]"); + AnsiConsole.MarkupLine("[gray]Type \"[red]/new[/]\" to start over.[/]"); + AnsiConsole.MarkupLine("[gray]Type \"[red]/exit[/]\" to leave the chat.[/]"); + + var chat = Ollama.Chat(stream => AnsiConsole.MarkupInterpolated($"[cyan]{stream?.Message.Content ?? ""}[/]")); - if (message.Equals("exit", StringComparison.OrdinalIgnoreCase)) - break; + if (!string.IsNullOrEmpty(systemPrompt)) + chat.SetMessages([new Message { Role = ChatRole.System, Content = systemPrompt }]); - var imageMatches = Regex.Matches(message, "{([^}]*)}").Select(m => m.Value); - var imageCount = imageMatches.Count(); - var hasImages = imageCount > 0; + string message; - if (hasImages) + do { - byte[][] imageBytes; - var imagePathsWithCurlyBraces = Regex.Matches(message, "{([^}]*)}").Select(m => m.Value); - var imagePaths = Regex.Matches(message, "{([^}]*)}").Select(m => m.Groups[1].Value); + AnsiConsole.WriteLine(); + message = ReadInput(); - try + if (message.Equals("/exit", StringComparison.OrdinalIgnoreCase)) { - imageBytes = imagePaths.Select(File.ReadAllBytes).ToArray(); + keepChatting = false; + break; } - catch (IOException ex) + + if (message.Equals("/new", StringComparison.OrdinalIgnoreCase)) { - AnsiConsole.MarkupLineInterpolated($"Could not load your {(imageCount == 1 ? "image" : "images")}:"); - AnsiConsole.MarkupLineInterpolated($"[red]{Markup.Escape(ex.Message)}[/]"); - AnsiConsole.MarkupLine("Please try again"); - continue; + keepChatting = true; + break; } - var imagesBase64 = imageBytes.Select(Convert.ToBase64String); + var imageMatches = Regex.Matches(message, "{([^}]*)}").Select(m => m.Value); + var imageCount = imageMatches.Count(); + var hasImages = imageCount > 0; - foreach (var path in imagePathsWithCurlyBraces) - message = message.Replace(path, ""); - - AnsiConsole.WriteLine(); - AnsiConsole.MarkupLine("[yellow]Image chat will only work with multimodal models like llava![/]"); - AnsiConsole.MarkupLine("[gray]Image paths have been removed from your message, sending this:[/]"); - AnsiConsole.MarkupLineInterpolated($"[silver]{Markup.Escape(message)}[/]"); - AnsiConsole.WriteLine(); - if (imageCount == 1) - AnsiConsole.MarkupLineInterpolated($"[gray]{"Here is the image, that is sent to the chat model in addition to your message."}[/]"); + if (hasImages) + { + byte[][] imageBytes; + var imagePathsWithCurlyBraces = Regex.Matches(message, "{([^}]*)}").Select(m => m.Value); + var imagePaths = Regex.Matches(message, "{([^}]*)}").Select(m => m.Groups[1].Value); + + try + { + imageBytes = imagePaths.Select(File.ReadAllBytes).ToArray(); + } + catch (IOException ex) + { + AnsiConsole.MarkupLineInterpolated($"Could not load your {(imageCount == 1 ? "image" : "images")}:"); + AnsiConsole.MarkupLineInterpolated($"[red]{Markup.Escape(ex.Message)}[/]"); + AnsiConsole.MarkupLine("Please try again"); + continue; + } + + var imagesBase64 = imageBytes.Select(Convert.ToBase64String); + + foreach (var path in imagePathsWithCurlyBraces) + message = message.Replace(path, ""); + + AnsiConsole.WriteLine(); + AnsiConsole.MarkupLine("[yellow]Image chat will only work with multimodal models like llava![/]"); + AnsiConsole.MarkupLine("[gray]Image paths have been removed from your message, sending this:[/]"); + AnsiConsole.MarkupLineInterpolated($"[silver]{Markup.Escape(message)}[/]"); + AnsiConsole.WriteLine(); + if (imageCount == 1) + AnsiConsole.MarkupLineInterpolated($"[gray]{"Here is the image, that is sent to the chat model in addition to your message."}[/]"); + else + AnsiConsole.MarkupLineInterpolated($"[gray]{"Here are the images, that are sent to the chat model in addition to your message."}[/]"); + AnsiConsole.WriteLine(); + + foreach (var consoleImage in imageBytes.Select(bytes => new CanvasImage(bytes))) + { + consoleImage.MaxWidth = 40; + AnsiConsole.Write(consoleImage); + } + + AnsiConsole.WriteLine(); + if (imageCount == 1) + AnsiConsole.MarkupLineInterpolated($"[gray]{"The image was scaled down for the console only, the model gets the full version."}[/]"); + else + AnsiConsole.MarkupLineInterpolated($"[gray]{"The images were scaled down for the console only, the model gets full versions."}[/]"); + AnsiConsole.WriteLine(); + + await chat.Send(message, [], imagesBase64); + } else - AnsiConsole.MarkupLineInterpolated($"[gray]{"Here are the images, that are sent to the chat model in addition to your message."}[/]"); - AnsiConsole.WriteLine(); - - foreach (var consoleImage in imageBytes.Select(bytes => new CanvasImage(bytes))) { - consoleImage.MaxWidth = 40; - AnsiConsole.Write(consoleImage); + await chat.Send(message); } AnsiConsole.WriteLine(); - if (imageCount == 1) - AnsiConsole.MarkupLineInterpolated($"[gray]{"The image was scaled down for the console only, the model gets the full version."}[/]"); - else - AnsiConsole.MarkupLineInterpolated($"[gray]{"The images were scaled down for the console only, the model gets full versions."}[/]"); - AnsiConsole.WriteLine(); - - await chat.Send(message, [], imagesBase64); - } - else - { - await chat.Send(message); - } - - AnsiConsole.WriteLine(); - } while (!string.IsNullOrEmpty(message)); + } while (!string.IsNullOrEmpty(message)); + } while (keepChatting); } } } \ No newline at end of file diff --git a/OllamaApiConsole/Demos/ToolConsole.cs b/OllamaApiConsole/Demos/ToolConsole.cs index 96380d5..942a721 100644 --- a/OllamaApiConsole/Demos/ToolConsole.cs +++ b/OllamaApiConsole/Demos/ToolConsole.cs @@ -19,47 +19,69 @@ public override async Task Run() if (!string.IsNullOrEmpty(Ollama.SelectedModel)) { - AnsiConsole.MarkupLineInterpolated($"You are talking to [blue]{Ollama.SelectedModel}[/] now."); - AnsiConsole.MarkupLineInterpolated($"When asked for the weather or the news for a given location, it will try to use a predefined tool."); - AnsiConsole.MarkupLineInterpolated($"If any tool is used, the intended usage information is printed."); - AnsiConsole.MarkupLine("[gray]Type \"[red]exit[/]\" to leave the chat.[/]"); - - var chat = Ollama.Chat(stream => AnsiConsole.MarkupInterpolated($"[cyan]{stream?.Message.Content ?? ""}[/]")); - string message; + var keepChatting = true; + var systemPrompt = ReadMultilineInput("Define a system prompt (optional)"); do { - AnsiConsole.WriteLine(); - message = ReadMultilineInput(); + AnsiConsole.MarkupLine(""); + AnsiConsole.MarkupLineInterpolated($"You are talking to [blue]{Ollama.SelectedModel}[/] now."); + AnsiConsole.MarkupLine("When asked for the weather or the news for a given location, it will try to use a predefined tool."); + AnsiConsole.MarkupLine("If any tool is used, the intended usage information is printed."); + AnsiConsole.MarkupLine("[gray]Submit your messages by hitting return twice.[/]"); + AnsiConsole.MarkupLine("[gray]Type \"[red]/new[/]\" to start over.[/]"); + AnsiConsole.MarkupLine("[gray]Type \"[red]/exit[/]\" to leave the chat.[/]"); - if (message.Equals("exit", StringComparison.OrdinalIgnoreCase)) - break; + var chat = Ollama.Chat(stream => AnsiConsole.MarkupInterpolated($"[cyan]{stream?.Message.Content ?? ""}[/]")); - try - { - await chat.SendAs(ChatRole.User, message, GetTools()); - } - catch (OllamaException ex) - { - AnsiConsole.MarkupLineInterpolated($"[red]{ex.Message}[/]"); - } + if (!string.IsNullOrEmpty(systemPrompt)) + chat.SetMessages([new Message { Role = ChatRole.System, Content = systemPrompt }]); - var toolCalls = chat.Messages.LastOrDefault()?.ToolCalls ?? []; - if (toolCalls.Any()) + string message; + + do { - AnsiConsole.MarkupLine("\n[purple]Tools used:[/]"); + AnsiConsole.WriteLine(); + message = ReadMultilineInput(); - foreach (var tool in toolCalls.Where(t => t.Function != null)) + if (message.Equals("/exit", StringComparison.OrdinalIgnoreCase)) { - AnsiConsole.MarkupLineInterpolated($" - [purple]{tool.Function!.Name}[/]"); + keepChatting = false; + break; + } - foreach (var argument in tool.Function.Arguments ?? []) - AnsiConsole.MarkupLineInterpolated($" - [purple]{argument.Key}[/]: [purple]{argument.Value}[/]"); + if (message.Equals("/new", StringComparison.OrdinalIgnoreCase)) + { + keepChatting = true; + break; + } + + try + { + await chat.SendAs(ChatRole.User, message, GetTools()); + } + catch (OllamaException ex) + { + AnsiConsole.MarkupLineInterpolated($"[red]{ex.Message}[/]"); + } + + var toolCalls = chat.Messages.LastOrDefault()?.ToolCalls ?? []; + if (toolCalls.Any()) + { + AnsiConsole.MarkupLine("\n[purple]Tools used:[/]"); + + foreach (var tool in toolCalls.Where(t => t.Function != null)) + { + AnsiConsole.MarkupLineInterpolated($" - [purple]{tool.Function!.Name}[/]"); + + foreach (var argument in tool.Function.Arguments ?? []) + AnsiConsole.MarkupLineInterpolated($" - [purple]{argument.Key}[/]: [purple]{argument.Value}[/]"); + } } - } - AnsiConsole.WriteLine(); - } while (!string.IsNullOrEmpty(message)); + AnsiConsole.WriteLine(); + } while (!string.IsNullOrEmpty(message)); + } while (keepChatting); } } diff --git a/OllamaApiConsole/OllamaConsole.cs b/OllamaApiConsole/OllamaConsole.cs index 9ad819c..db35ea7 100644 --- a/OllamaApiConsole/OllamaConsole.cs +++ b/OllamaApiConsole/OllamaConsole.cs @@ -1,3 +1,4 @@ +using System.Text; using OllamaSharp; using Spectre.Console; @@ -29,19 +30,17 @@ public static string ReadMultilineInput(string prompt = "", string additionalInf if (!string.IsNullOrEmpty(additionalInformation)) AnsiConsole.MarkupLine(additionalInformation); - AnsiConsole.MarkupLineInterpolated($"Type \"[red]-[/]\" and hit [red]{"[Return]"}[/] to submit."); - + var builder = new StringBuilder(); var input = ""; - while (!input.TrimEnd().EndsWith('-')) + while (!string.IsNullOrEmpty(input) || builder.Length == 0) { AnsiConsole.Markup("[blue]> [/]"); - input += Console.ReadLine() + Environment.NewLine; + input = Console.ReadLine(); + builder.AppendLine(input); } - input = input.TrimEnd(); - - return input.EndsWith('-') ? input.Substring(0, input.Length - 1) : input; + return builder.ToString().TrimEnd(); } protected async Task SelectModel(string prompt, string additionalInformation = "")