From 775fa4929364ac3e843bf001e5cbb9629d6fa2cc Mon Sep 17 00:00:00 2001 From: puchacz Date: Tue, 28 Jan 2025 21:08:46 +0100 Subject: [PATCH] Raised up ollama sharp plugin --- .../OllamaApiPlayground.cs | 19 +++++- .../OllamaQueryTests.cs | 4 +- .../OllamaSingleRowSourceTests.cs | 2 +- .../CompletionResponse.cs | 21 ++++++ Musoq.DataSources.Ollama/IOllamaApi.cs | 8 +-- .../Musoq.DataSources.Ollama.csproj | 4 +- Musoq.DataSources.Ollama/OllamaApi.cs | 65 +++++++++++-------- Musoq.DataSources.Ollama/OllamaEntityBase.cs | 4 +- Musoq.DataSources.Ollama/OllamaLibrary.cs | 20 ++---- .../OllamaSingleRowSource.cs | 28 ++++---- 10 files changed, 107 insertions(+), 68 deletions(-) create mode 100644 Musoq.DataSources.Ollama/CompletionResponse.cs diff --git a/Musoq.DataSources.Ollama.Tests/OllamaApiPlayground.cs b/Musoq.DataSources.Ollama.Tests/OllamaApiPlayground.cs index a9bc5a5..25fa583 100644 --- a/Musoq.DataSources.Ollama.Tests/OllamaApiPlayground.cs +++ b/Musoq.DataSources.Ollama.Tests/OllamaApiPlayground.cs @@ -14,7 +14,7 @@ public void DoSomeRealTests() var library = new OllamaLibrary(); var entity = new OllamaEntity( new OllamaApi(), - "llava:13b", + "llama3.2-vision:latest", 0, CancellationToken.None); @@ -25,4 +25,21 @@ public void DoSomeRealTests() "How would you name this photo(filename)?", library.ToBase64(File.ReadAllBytes("D:\\Photos\\Piotruś\\iphone\\202403__\\FIIG0678.JPG"))); } + + [Ignore] + [TestMethod] + public void DoSomeOtherRealTests() + { + var library = new OllamaLibrary(); + var entity = new OllamaEntity( + new OllamaApi(), + "llama3.2-vision:latest", + 0, + CancellationToken.None); + + var response = library.AskImage( + entity, + "Does the photo contain a cat? reply with \"yes\" or \"no\"", + library.ToBase64(File.ReadAllBytes("C:\\Users\\pucha\\OneDrive\\Pictures\\fotokozia\\jak-ze-starej-fotografii.jpg"))); + } } \ No newline at end of file diff --git a/Musoq.DataSources.Ollama.Tests/OllamaQueryTests.cs b/Musoq.DataSources.Ollama.Tests/OllamaQueryTests.cs index 31b7563..5178232 100644 --- a/Musoq.DataSources.Ollama.Tests/OllamaQueryTests.cs +++ b/Musoq.DataSources.Ollama.Tests/OllamaQueryTests.cs @@ -199,9 +199,9 @@ private static Mock CreateOpenAiApiMock(string response) var mockOllamaApi = new Mock(); mockOllamaApi.Setup(x => x.GetCompletionAsync(It.IsAny(), It.IsAny>())) - .ReturnsAsync(new ConversationContextWithResponse(response, [])); + .ReturnsAsync(new CompletionResponse(response)); mockOllamaApi.Setup(x => x.GetImageCompletionAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new ConversationContextWithResponse(response, [])); + .ReturnsAsync(new CompletionResponse(response)); return mockOllamaApi; } diff --git a/Musoq.DataSources.Ollama.Tests/OllamaSingleRowSourceTests.cs b/Musoq.DataSources.Ollama.Tests/OllamaSingleRowSourceTests.cs index 6d149f3..714fa14 100644 --- a/Musoq.DataSources.Ollama.Tests/OllamaSingleRowSourceTests.cs +++ b/Musoq.DataSources.Ollama.Tests/OllamaSingleRowSourceTests.cs @@ -207,7 +207,7 @@ private static Mock PrepareOpenAiApi(string response) It.IsAny>())) .Returns>((entity, messages) => Task.FromResult( - new ConversationContextWithResponse(response, []))); + new CompletionResponse(response))); return mock; } } \ No newline at end of file diff --git a/Musoq.DataSources.Ollama/CompletionResponse.cs b/Musoq.DataSources.Ollama/CompletionResponse.cs new file mode 100644 index 0000000..1d94398 --- /dev/null +++ b/Musoq.DataSources.Ollama/CompletionResponse.cs @@ -0,0 +1,21 @@ +namespace Musoq.DataSources.Ollama; + +/// +/// Completion response +/// +public class CompletionResponse +{ + /// + /// Initializes a new instance of the class. + /// + /// The text + public CompletionResponse(string text) + { + Text = text; + } + + /// + /// Gets or sets the text + /// + public string Text { get; } +} \ No newline at end of file diff --git a/Musoq.DataSources.Ollama/IOllamaApi.cs b/Musoq.DataSources.Ollama/IOllamaApi.cs index 0e88269..e3207d5 100644 --- a/Musoq.DataSources.Ollama/IOllamaApi.cs +++ b/Musoq.DataSources.Ollama/IOllamaApi.cs @@ -14,14 +14,14 @@ public interface IOllamaApi /// /// The entity /// Messages - /// ConversationContextWithResponse - Task GetCompletionAsync(OllamaEntityBase entity, IList messages); + /// CompletionResponse + Task GetCompletionAsync(OllamaEntityBase entity, IList messages); /// /// Gets the image completion from Ollama API /// /// The entity /// Message - /// ConversationContextWithResponse - Task GetImageCompletionAsync(OllamaEntityBase entity, Message message); + /// CompletionResponse + Task GetImageCompletionAsync(OllamaEntityBase entity, Message message); } \ No newline at end of file diff --git a/Musoq.DataSources.Ollama/Musoq.DataSources.Ollama.csproj b/Musoq.DataSources.Ollama/Musoq.DataSources.Ollama.csproj index 3042175..3981d23 100644 --- a/Musoq.DataSources.Ollama/Musoq.DataSources.Ollama.csproj +++ b/Musoq.DataSources.Ollama/Musoq.DataSources.Ollama.csproj @@ -5,7 +5,7 @@ enable enable true - 3.2.13 + 3.2.14 Jakub Puchała Musoq https://github.com/Puchaczov/Musoq @@ -27,7 +27,7 @@ - + runtime diff --git a/Musoq.DataSources.Ollama/OllamaApi.cs b/Musoq.DataSources.Ollama/OllamaApi.cs index 29397b9..3cc4bc2 100644 --- a/Musoq.DataSources.Ollama/OllamaApi.cs +++ b/Musoq.DataSources.Ollama/OllamaApi.cs @@ -30,49 +30,62 @@ public OllamaApi(string address) }); } - public async Task GetImageCompletionAsync(OllamaEntityBase entity, Message message) + public async Task GetImageCompletionAsync(OllamaEntityBase entity, Message message) { - var completion = await _api.GetCompletion(new GenerateCompletionRequest() + StringBuilder modelResponse = new(); + var chatRequest = new ChatRequest { Model = entity.Model, - Context = [], - Images = message.Images, - Prompt = message.Content, - Stream = false, - Options = new RequestOptions() + Messages = new List + { + message + }, + Options = new RequestOptions { Temperature = entity.Temperature - } - }); + }, + Stream = true + }; + + await foreach (var token in _api.ChatAsync(chatRequest, entity.CancellationToken)) + { + if (token is null) + continue; + + if (token.Done) + break; + + modelResponse.Append(token.Message.Content); + } - return completion; + return new CompletionResponse(modelResponse.ToString()); } - public async Task GetCompletionAsync(OllamaEntityBase entity, IList messages) + public async Task GetCompletionAsync(OllamaEntityBase entity, IList messages) { - TaskCompletionSource completionSource = new(); - var stringResponse = new StringBuilder(); - - await _api.SendChat(new ChatRequest() + StringBuilder modelResponse = new(); + var chatRequest = new ChatRequest { Model = entity.Model, Messages = messages, - Options = new RequestOptions() + Options = new RequestOptions { - Temperature = entity.Temperature, + Temperature = entity.Temperature }, Stream = true - }, stream => + }; + + await foreach (var token in _api.ChatAsync(chatRequest, entity.CancellationToken)) { - if (!stream.Done) - { - stringResponse.Append(stream.Message.Content); - return; - } + if (token is null) + continue; + + if (token.Done) + break; - completionSource.SetResult(new ConversationContextWithResponse(stringResponse.ToString(), [])); - }, entity.CancellationToken); + modelResponse.Append(token.Message.Content); + } - return completionSource.Task.Result; + return new CompletionResponse(modelResponse.ToString()); } } \ No newline at end of file diff --git a/Musoq.DataSources.Ollama/OllamaEntityBase.cs b/Musoq.DataSources.Ollama/OllamaEntityBase.cs index 7060f07..9a151a8 100644 --- a/Musoq.DataSources.Ollama/OllamaEntityBase.cs +++ b/Musoq.DataSources.Ollama/OllamaEntityBase.cs @@ -8,7 +8,7 @@ public abstract class OllamaEntityBase /// /// Initializes a new instance of the class with the specified parameters. /// - protected OllamaEntityBase(IOllamaApi api, string? model, float temperature, CancellationToken cancellationToken) + protected OllamaEntityBase(IOllamaApi api, string model, float temperature, CancellationToken cancellationToken) { Api = api; Model = model; @@ -24,7 +24,7 @@ protected OllamaEntityBase(IOllamaApi api, string? model, float temperature, Can /// /// Gets the optional model name to use for generating text. /// - public string? Model { get; } + public string Model { get; } /// /// Gets the temperature to control the randomness of the generated text. diff --git a/Musoq.DataSources.Ollama/OllamaLibrary.cs b/Musoq.DataSources.Ollama/OllamaLibrary.cs index 19f11a0..d2dcdf8 100644 --- a/Musoq.DataSources.Ollama/OllamaLibrary.cs +++ b/Musoq.DataSources.Ollama/OllamaLibrary.cs @@ -2,7 +2,6 @@ using Musoq.Plugins; using Musoq.Plugins.Attributes; using Newtonsoft.Json; -using OllamaSharp; using OllamaSharp.Models.Chat; using SharpToken; @@ -213,7 +212,7 @@ public bool IsQuestionApplicableToImage([InjectSpecificSource(typeof(OllamaEntit var youAreImageDescriberDescribeTheImage = $"You are image based question answerer. Return only answer for the following question: {question}. You must respond with json {{ result: boolean }}. Do not comment or explain anything."; return api.GetImageCompletionAsync( entity, - new(ChatRole.User, youAreImageDescriberDescribeTheImage, [imageBase64])); + new Message(ChatRole.User, youAreImageDescriberDescribeTheImage, [imageBase64])); }); var response = describeImageResultTask.Result; @@ -280,7 +279,7 @@ public int CountTokens(string content) return encoding.CountTokens(content); } - private static async Task DoAsynchronously(Func> func) + private static async Task DoAsynchronously(Func> func) { var result = func(); @@ -288,21 +287,16 @@ private static async Task DoAsynchronously(Func Rows - { - get - { - return new IObjectResolver[] - { - new EntityResolver( - new OllamaEntity( - _openAiApi, - _openAiRequestInfo.Model, - _openAiRequestInfo.Temperature, - _cancellationToken), - OllamaSchemaHelper.NameToIndexMap, - OllamaSchemaHelper.IndexToMethodAccessMap) - }; - } - } + public override IEnumerable Rows => + [ + new EntityResolver( + new OllamaEntity( + _openAiApi, + _openAiRequestInfo.Model, + _openAiRequestInfo.Temperature, + _cancellationToken), + OllamaSchemaHelper.NameToIndexMap, + OllamaSchemaHelper.IndexToMethodAccessMap) + ]; } \ No newline at end of file