From 88f8e65d724f518ae4ae02b07934f5ea2c52f1ad Mon Sep 17 00:00:00 2001 From: Milkey Tan <24996957+mili-tan@users.noreply.github.com> Date: Wed, 15 Jan 2025 05:46:08 +0800 Subject: [PATCH 1/5] Update CreateModelRequest --- src/Constants/Application.cs | 4 +++ src/Models/CreateModel.cs | 62 +++++++++++++++++++++++++------- src/OllamaApiClientExtensions.cs | 46 ------------------------ 3 files changed, 54 insertions(+), 58 deletions(-) diff --git a/src/Constants/Application.cs b/src/Constants/Application.cs index 6b5a118..1f715e8 100644 --- a/src/Constants/Application.cs +++ b/src/Constants/Application.cs @@ -101,4 +101,8 @@ internal static class Application public const string Completed = "completed"; public const string Embeddings = "embeddings"; public const string ParameterSize = "parameter_size"; + public const string Messages = "message"; + public const string Adapters = "adapters"; + public const string Files = "files"; + public const string From = "from"; } \ No newline at end of file diff --git a/src/Models/CreateModel.cs b/src/Models/CreateModel.cs index 85c8a5e..9af5a83 100644 --- a/src/Models/CreateModel.cs +++ b/src/Models/CreateModel.cs @@ -1,14 +1,17 @@ using System.Text.Json.Serialization; using OllamaSharp.Constants; +using OllamaSharp.Models.Chat; namespace OllamaSharp.Models; /// -/// Create a model from a Modelfile. It is recommended to set to the -/// content of the Modelfile rather than just set path. This is a requirement -/// for remote create. Remote model creation must also create any file blobs, -/// fields such as FROM and ADAPTER, explicitly with the server using Create a -/// Blob and the value to the path indicated in the response. +/// Create a model from: +/// another model; +/// a safetensors directory; or +/// a GGUF file. +/// If you are creating a model from a safetensors directory or from a GGUF file, +/// you must [create a blob] for each of the files and then use the file name and SHA256 +/// digest associated with each blob in the `files` field. /// /// Ollama API docs /// @@ -23,17 +26,52 @@ public class CreateModelRequest : OllamaRequest public string? Model { get; set; } /// - /// Contents of the Modelfile - /// See https://github.com/jmorganca/ollama/blob/main/docs/modelfile.md + /// Name of an existing model to create the new model from (optional) /// - [JsonPropertyName(Application.ModelFile)] - public string ModelFileContent { get; set; } = null!; + [JsonPropertyName(Application.From)] + public string? From { get; set; } /// - /// Path to the Modelfile (optional) + /// A dictionary of file names to SHA256 digests of blobs to create the model from (optional) /// - [JsonPropertyName(Application.Path)] - public string? Path { get; set; } + [JsonPropertyName(Application.Files)] + public Dictionary? Files { get; set; } + + /// + /// A dictionary of file names to SHA256 digests of blobs for LORA adapters (optional) + /// + [JsonPropertyName(Application.Adapters)] + public Dictionary? Adapters { get; set; } + + /// + /// The prompt template for the model (optional) + /// + [JsonPropertyName(Application.Template)] + public string? Template { get; set; } + + /// + /// A string or list of strings containing the license or licenses for the model (optional) + /// + [JsonPropertyName(Application.License)] + public object? License { get; set; } + + /// + /// A string containing the system prompt for the model (optional) + /// + [JsonPropertyName(Application.System)] + public string? System { get; set; } + + /// + /// A dictionary of parameters for the model (optional) + /// + [JsonPropertyName(Application.Parameters)] + public Dictionary? Parameters { get; set; } + + /// + /// A list of message objects used to create a conversation (optional) + /// + [JsonPropertyName(Application.Messages)] + public IEnumerable? Messages { get; set; } /// /// If false the response will be returned as a single response object, rather than a stream of objects (optional) diff --git a/src/OllamaApiClientExtensions.cs b/src/OllamaApiClientExtensions.cs index 1a99dcb..4f7fc80 100644 --- a/src/OllamaApiClientExtensions.cs +++ b/src/OllamaApiClientExtensions.cs @@ -18,52 +18,6 @@ public static class OllamaApiClientExtensions public static Task CopyModelAsync(this IOllamaApiClient client, string source, string destination, CancellationToken cancellationToken = default) => client.CopyModelAsync(new CopyModelRequest { Source = source, Destination = destination }, cancellationToken); - /// - /// Sends a request to the /api/create endpoint to create a model. - /// - /// The client used to execute the command. - /// The name for the new model. - /// - /// The file content for the model file the new model should be built with. - /// See . - /// - /// The token to cancel the operation with. - /// An async enumerable that can be used to iterate over the streamed responses. See . - public static IAsyncEnumerable CreateModelAsync(this IOllamaApiClient client, string name, string modelFileContent, CancellationToken cancellationToken = default) - { - var request = new CreateModelRequest - { - Model = name, - ModelFileContent = modelFileContent, - Stream = true - }; - return client.CreateModelAsync(request, cancellationToken); - } - - /// - /// Sends a request to the /api/create endpoint to create a model. - /// - /// The client used to execute the command. - /// The name for the new model. - /// - /// The file content for the model file the new model should be built with. - /// See . - /// - /// The name path to the model file. - /// The token to cancel the operation with. - /// An async enumerable that can be used to iterate over the streamed responses. See . - public static IAsyncEnumerable CreateModelAsync(this IOllamaApiClient client, string name, string modelFileContent, string path, CancellationToken cancellationToken = default) - { - var request = new CreateModelRequest - { - Model = name, - ModelFileContent = modelFileContent, - Path = path, - Stream = true - }; - return client.CreateModelAsync(request, cancellationToken); - } - /// /// Sends a request to the /api/delete endpoint to delete a model. /// From 1374232234e69ab4ac1106728399db3678e89c2a Mon Sep 17 00:00:00 2001 From: Milkey Tan <24996957+mili-tan@users.noreply.github.com> Date: Wed, 15 Jan 2025 05:55:39 +0800 Subject: [PATCH 2/5] Add PushBolbAsync and IsBolbExistsAsync --- demo/Demos/ModelManagerConsole.cs | 20 +++++++++---------- src/IOllamaApiClient.cs | 14 +++++++++++++ src/OllamaApiClient.cs | 21 ++++++++++++++++---- test/FunctionalTests/OllamaApiClientTests.cs | 10 +--------- test/TestOllamaApiClient.cs | 10 ++++++++++ 5 files changed, 52 insertions(+), 23 deletions(-) diff --git a/demo/Demos/ModelManagerConsole.cs b/demo/Demos/ModelManagerConsole.cs index 93c3992..65eadc7 100644 --- a/demo/Demos/ModelManagerConsole.cs +++ b/demo/Demos/ModelManagerConsole.cs @@ -28,9 +28,9 @@ public override async Task Run() await CopyModel(); break; - case "Create model": - await CreateModel(); - break; + //case "Create model": + // await CreateModel(); + // break; case "Delete model": await DeleteModel(); @@ -75,13 +75,13 @@ private async Task CopyModel() } } - private async Task CreateModel() - { - var createName = ReadInput("Enter a name for your new model:"); - var createModelFileContent = ReadInput("Enter the contents for the model file:", $"[{HintTextColor}]See [/][{AccentTextColor}][link]https://ollama.ai/library[/][/][{HintTextColor}] for available models[/]"); - await foreach (var status in Ollama.CreateModelAsync(createName, createModelFileContent)) - AnsiConsole.MarkupLineInterpolated($"{status?.Status ?? ""}"); - } + //private async Task CreateModel() + //{ + // var createName = ReadInput("Enter a name for your new model:"); + // var createModelFileContent = ReadInput("Enter the contents for the model file:", $"[{HintTextColor}]See [/][{AccentTextColor}][link]https://ollama.ai/library[/][/][{HintTextColor}] for available models[/]"); + // await foreach (var status in Ollama.CreateModelAsync(createName, createModelFileContent)) + // AnsiConsole.MarkupLineInterpolated($"{status?.Status ?? ""}"); + //} private async Task DeleteModel() { diff --git a/src/IOllamaApiClient.cs b/src/IOllamaApiClient.cs index 7bf03a2..96ccf68 100644 --- a/src/IOllamaApiClient.cs +++ b/src/IOllamaApiClient.cs @@ -132,4 +132,18 @@ public interface IOllamaApiClient /// The token to cancel the operation with. /// A task that represents the asynchronous operation. The task result contains the . Task GetVersionAsync(CancellationToken cancellationToken = default); + + /// + /// Push a file to the Ollama server to create a "blob" (Binary Large Object). + /// + /// The expected SHA256 digest of the file. + /// The bytes data of the file. + /// The token to cancel the operation with. + Task PushBolbAsync(string digest, byte[] bytes, CancellationToken cancellationToken = default); + /// + /// Ensures that the file blob (Binary Large Object) used with create a model exists on the server. This checks your Ollama server and not ollama.com. + /// + /// The expected SHA256 digest of the file. + /// The token to cancel the operation with. + Task IsBolbExistsAsync(string digest, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/OllamaApiClient.cs b/src/OllamaApiClient.cs index ca20966..d7585f2 100644 --- a/src/OllamaApiClient.cs +++ b/src/OllamaApiClient.cs @@ -236,15 +236,28 @@ public async Task GetVersionAsync(CancellationToken cancellationToken = private async Task GetAsync(string endpoint, CancellationToken cancellationToken) { using var requestMessage = CreateRequestMessage(HttpMethod.Get, endpoint); - using var response = await SendToOllamaAsync(requestMessage, null, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false); - using var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); - return (await JsonSerializer.DeserializeAsync(responseStream, IncomingJsonSerializerOptions, cancellationToken))!; } - + /// + public async Task PushBolbAsync(string digest, byte[] bytes, CancellationToken cancellationToken = default) + { + using var requestMessage = new HttpRequestMessage(HttpMethod.Post, "api/blobs/" + digest); + requestMessage.Content = new ByteArrayContent(bytes); + using var response = await SendToOllamaAsync(requestMessage, null, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + } + + /// + public async Task IsBolbExistsAsync(string digest, CancellationToken cancellationToken = default) + { + using var requestMessage = new HttpRequestMessage(HttpMethod.Head, "api/blobs/" + digest); + requestMessage.ApplyCustomHeaders(DefaultRequestHeaders, null); + var response = await _client.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false); + return response.StatusCode == HttpStatusCode.OK; + } private async Task PostAsync(string endpoint, TRequest ollamaRequest, CancellationToken cancellationToken) where TRequest : OllamaRequest { diff --git a/test/FunctionalTests/OllamaApiClientTests.cs b/test/FunctionalTests/OllamaApiClientTests.cs index ce0f70e..a8fed64 100644 --- a/test/FunctionalTests/OllamaApiClientTests.cs +++ b/test/FunctionalTests/OllamaApiClientTests.cs @@ -70,15 +70,7 @@ public async Task CreateModel() var model = new CreateModelRequest { Model = _localModel, - ModelFileContent = - """ - FROM llama3.2 - PARAMETER temperature 0.3 - PARAMETER num_ctx 100 - - # sets a custom system message to specify the behavior of the chat assistant - SYSTEM You are a concise model that tries to return yes or no answers. - """ + From = _model }; var response = await _client diff --git a/test/TestOllamaApiClient.cs b/test/TestOllamaApiClient.cs index 00c9b60..92fe297 100644 --- a/test/TestOllamaApiClient.cs +++ b/test/TestOllamaApiClient.cs @@ -69,6 +69,16 @@ public Task GetVersionAsync(CancellationToken cancellationToken = defau throw new NotImplementedException(); } + public Task PushBolbAsync(string digest, byte[] bytes, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task IsBolbExistsAsync(string digest, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + public Task IsRunningAsync(CancellationToken cancellationToken = default) { throw new NotImplementedException(); From 7c88d92cba8c0bbae08da8f0d5135de516a6bf29 Mon Sep 17 00:00:00 2001 From: Milkey Tan <24996957+mili-tan@users.noreply.github.com> Date: Wed, 15 Jan 2025 06:08:02 +0800 Subject: [PATCH 3/5] Update OllamaApiClient --- src/IOllamaApiClient.cs | 1 + src/OllamaApiClient.cs | 39 +++++++++++++++++++++------------------ 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/IOllamaApiClient.cs b/src/IOllamaApiClient.cs index 96ccf68..859130d 100644 --- a/src/IOllamaApiClient.cs +++ b/src/IOllamaApiClient.cs @@ -140,6 +140,7 @@ public interface IOllamaApiClient /// The bytes data of the file. /// The token to cancel the operation with. Task PushBolbAsync(string digest, byte[] bytes, CancellationToken cancellationToken = default); + /// /// Ensures that the file blob (Binary Large Object) used with create a model exists on the server. This checks your Ollama server and not ollama.com. /// diff --git a/src/OllamaApiClient.cs b/src/OllamaApiClient.cs index d7585f2..06d3654 100644 --- a/src/OllamaApiClient.cs +++ b/src/OllamaApiClient.cs @@ -219,6 +219,24 @@ public async Task GetVersionAsync(CancellationToken cancellationToken = return Version.Parse(versionString); } + /// + public async Task PushBolbAsync(string digest, byte[] bytes, CancellationToken cancellationToken = default) + { + using var requestMessage = new HttpRequestMessage(HttpMethod.Post, "api/blobs/" + digest); + requestMessage.Content = new ByteArrayContent(bytes); + using var response = await SendToOllamaAsync(requestMessage, null, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + } + + /// + public async Task IsBolbExistsAsync(string digest, CancellationToken cancellationToken = default) + { + using var requestMessage = new HttpRequestMessage(HttpMethod.Head, "api/blobs/" + digest); + requestMessage.ApplyCustomHeaders(DefaultRequestHeaders, null); + var response = await _client.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false); + return response.StatusCode == HttpStatusCode.OK; + } + private async IAsyncEnumerable GenerateCompletionAsync(GenerateRequest generateRequest, [EnumeratorCancellation] CancellationToken cancellationToken) { using var requestMessage = CreateRequestMessage(HttpMethod.Post, Endpoints.Generate, generateRequest); @@ -236,27 +254,12 @@ public async Task GetVersionAsync(CancellationToken cancellationToken = private async Task GetAsync(string endpoint, CancellationToken cancellationToken) { using var requestMessage = CreateRequestMessage(HttpMethod.Get, endpoint); - using var response = await SendToOllamaAsync(requestMessage, null, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false); - using var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); - return (await JsonSerializer.DeserializeAsync(responseStream, IncomingJsonSerializerOptions, cancellationToken))!; - } - /// - public async Task PushBolbAsync(string digest, byte[] bytes, CancellationToken cancellationToken = default) - { - using var requestMessage = new HttpRequestMessage(HttpMethod.Post, "api/blobs/" + digest); - requestMessage.Content = new ByteArrayContent(bytes); using var response = await SendToOllamaAsync(requestMessage, null, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false); - response.EnsureSuccessStatusCode(); - } - /// - public async Task IsBolbExistsAsync(string digest, CancellationToken cancellationToken = default) - { - using var requestMessage = new HttpRequestMessage(HttpMethod.Head, "api/blobs/" + digest); - requestMessage.ApplyCustomHeaders(DefaultRequestHeaders, null); - var response = await _client.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false); - return response.StatusCode == HttpStatusCode.OK; + using var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + + return (await JsonSerializer.DeserializeAsync(responseStream, IncomingJsonSerializerOptions, cancellationToken))!; } private async Task PostAsync(string endpoint, TRequest ollamaRequest, CancellationToken cancellationToken) where TRequest : OllamaRequest From 007ee87a067c7a2da40d57b9e60337c9c6a31e67 Mon Sep 17 00:00:00 2001 From: Milkey Tan <24996957+mili-tan@users.noreply.github.com> Date: Wed, 15 Jan 2025 06:26:06 +0800 Subject: [PATCH 4/5] Add OllamaApiClientExtensions.PushBolbAsync; Update ModelManagerConsole --- demo/Demos/ModelManagerConsole.cs | 22 ++++++++++++---------- src/OllamaApiClientExtensions.cs | 10 ++++++++++ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/demo/Demos/ModelManagerConsole.cs b/demo/Demos/ModelManagerConsole.cs index 65eadc7..0db9d3c 100644 --- a/demo/Demos/ModelManagerConsole.cs +++ b/demo/Demos/ModelManagerConsole.cs @@ -28,9 +28,9 @@ public override async Task Run() await CopyModel(); break; - //case "Create model": - // await CreateModel(); - // break; + case "Create model": + await CreateModel(); + break; case "Delete model": await DeleteModel(); @@ -75,13 +75,15 @@ private async Task CopyModel() } } - //private async Task CreateModel() - //{ - // var createName = ReadInput("Enter a name for your new model:"); - // var createModelFileContent = ReadInput("Enter the contents for the model file:", $"[{HintTextColor}]See [/][{AccentTextColor}][link]https://ollama.ai/library[/][/][{HintTextColor}] for available models[/]"); - // await foreach (var status in Ollama.CreateModelAsync(createName, createModelFileContent)) - // AnsiConsole.MarkupLineInterpolated($"{status?.Status ?? ""}"); - //} + private async Task CreateModel() + { + var createName = ReadInput("Enter a name for your new model:"); + var fromModel = ReadInput("Enter the name of the model to create from:", + $"[{HintTextColor}]See [/][{AccentTextColor}][link]https://ollama.ai/library[/][/][{HintTextColor}] for available models[/]"); + var systemPrompt = ReadInput("Set a new system prompt word for the model:"); + await foreach (var status in Ollama.CreateModelAsync(new CreateModelRequest { From = fromModel, System = systemPrompt, Model = createName })) + AnsiConsole.MarkupLineInterpolated($"{status?.Status ?? ""}"); + } private async Task DeleteModel() { diff --git a/src/OllamaApiClientExtensions.cs b/src/OllamaApiClientExtensions.cs index 4f7fc80..a06f175 100644 --- a/src/OllamaApiClientExtensions.cs +++ b/src/OllamaApiClientExtensions.cs @@ -1,3 +1,4 @@ +using System.Security.Cryptography; using OllamaSharp.Models; namespace OllamaSharp; @@ -98,4 +99,13 @@ public static Task EmbedAsync(this IOllamaApiClient client, strin /// A task that represents the asynchronous operation. The task result contains the with the model information. public static Task ShowModelAsync(this IOllamaApiClient client, string model, CancellationToken cancellationToken = default) => client.ShowModelAsync(new ShowModelRequest { Model = model }, cancellationToken); + + /// + /// Push a file to the Ollama server to create a "blob" (Binary Large Object). + /// + /// The client used to execute the command. + /// The bytes data of the file. + /// The token to cancel the operation with. + public static Task PushBolbAsync(this IOllamaApiClient client, byte[] bytes, CancellationToken cancellationToken = default) + => client.PushBolbAsync($"sha256:{BitConverter.ToString(SHA256.Create().ComputeHash(bytes)).Replace("-", string.Empty).ToLower()}", bytes, cancellationToken); } From 13b725a728bae45cb1216bfe76fc46aea4f73a46 Mon Sep 17 00:00:00 2001 From: Milkey Tan <24996957+mili-tan@users.noreply.github.com> Date: Wed, 15 Jan 2025 06:31:42 +0800 Subject: [PATCH 5/5] fix Typo Blob --- src/IOllamaApiClient.cs | 4 ++-- src/OllamaApiClient.cs | 4 ++-- src/OllamaApiClientExtensions.cs | 4 ++-- test/TestOllamaApiClient.cs | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/IOllamaApiClient.cs b/src/IOllamaApiClient.cs index 859130d..c374d45 100644 --- a/src/IOllamaApiClient.cs +++ b/src/IOllamaApiClient.cs @@ -139,12 +139,12 @@ public interface IOllamaApiClient /// The expected SHA256 digest of the file. /// The bytes data of the file. /// The token to cancel the operation with. - Task PushBolbAsync(string digest, byte[] bytes, CancellationToken cancellationToken = default); + Task PushBlobAsync(string digest, byte[] bytes, CancellationToken cancellationToken = default); /// /// Ensures that the file blob (Binary Large Object) used with create a model exists on the server. This checks your Ollama server and not ollama.com. /// /// The expected SHA256 digest of the file. /// The token to cancel the operation with. - Task IsBolbExistsAsync(string digest, CancellationToken cancellationToken = default); + Task IsBlobExistsAsync(string digest, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/OllamaApiClient.cs b/src/OllamaApiClient.cs index 06d3654..4bfa882 100644 --- a/src/OllamaApiClient.cs +++ b/src/OllamaApiClient.cs @@ -220,7 +220,7 @@ public async Task GetVersionAsync(CancellationToken cancellationToken = } /// - public async Task PushBolbAsync(string digest, byte[] bytes, CancellationToken cancellationToken = default) + public async Task PushBlobAsync(string digest, byte[] bytes, CancellationToken cancellationToken = default) { using var requestMessage = new HttpRequestMessage(HttpMethod.Post, "api/blobs/" + digest); requestMessage.Content = new ByteArrayContent(bytes); @@ -229,7 +229,7 @@ public async Task PushBolbAsync(string digest, byte[] bytes, CancellationToken c } /// - public async Task IsBolbExistsAsync(string digest, CancellationToken cancellationToken = default) + public async Task IsBlobExistsAsync(string digest, CancellationToken cancellationToken = default) { using var requestMessage = new HttpRequestMessage(HttpMethod.Head, "api/blobs/" + digest); requestMessage.ApplyCustomHeaders(DefaultRequestHeaders, null); diff --git a/src/OllamaApiClientExtensions.cs b/src/OllamaApiClientExtensions.cs index a06f175..3993cf6 100644 --- a/src/OllamaApiClientExtensions.cs +++ b/src/OllamaApiClientExtensions.cs @@ -106,6 +106,6 @@ public static Task ShowModelAsync(this IOllamaApiClient clien /// The client used to execute the command. /// The bytes data of the file. /// The token to cancel the operation with. - public static Task PushBolbAsync(this IOllamaApiClient client, byte[] bytes, CancellationToken cancellationToken = default) - => client.PushBolbAsync($"sha256:{BitConverter.ToString(SHA256.Create().ComputeHash(bytes)).Replace("-", string.Empty).ToLower()}", bytes, cancellationToken); + public static Task PushBlobAsync(this IOllamaApiClient client, byte[] bytes, CancellationToken cancellationToken = default) + => client.PushBlobAsync($"sha256:{BitConverter.ToString(SHA256.Create().ComputeHash(bytes)).Replace("-", string.Empty).ToLower()}", bytes, cancellationToken); } diff --git a/test/TestOllamaApiClient.cs b/test/TestOllamaApiClient.cs index 92fe297..0627188 100644 --- a/test/TestOllamaApiClient.cs +++ b/test/TestOllamaApiClient.cs @@ -69,12 +69,12 @@ public Task GetVersionAsync(CancellationToken cancellationToken = defau throw new NotImplementedException(); } - public Task PushBolbAsync(string digest, byte[] bytes, CancellationToken cancellationToken = default) + public Task PushBlobAsync(string digest, byte[] bytes, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } - public Task IsBolbExistsAsync(string digest, CancellationToken cancellationToken = default) + public Task IsBlobExistsAsync(string digest, CancellationToken cancellationToken = default) { throw new NotImplementedException(); }