diff --git a/src/Cellm.Tests/Cellm.Tests.csproj b/src/Cellm.Tests/Cellm.Tests.csproj index 54c5c76..5685ae0 100644 --- a/src/Cellm.Tests/Cellm.Tests.csproj +++ b/src/Cellm.Tests/Cellm.Tests.csproj @@ -1,4 +1,4 @@ - + net8.0-windows @@ -12,8 +12,8 @@ - - + + diff --git a/src/Cellm.Tests/packages.lock.json b/src/Cellm.Tests/packages.lock.json index ebffdb9..f1fd292 100644 --- a/src/Cellm.Tests/packages.lock.json +++ b/src/Cellm.Tests/packages.lock.json @@ -4,11 +4,11 @@ "net8.0-windows7.0": { "ExcelDna.Testing": { "type": "Direct", - "requested": "[1.8.0, )", - "resolved": "1.8.0", - "contentHash": "nuxqQE7t9O8kASTeGrwdCwJl8p3A1PchmYUNEwmyZHfTviWgaKJpSoR5m2S/c4jr0ejErgXooKKkzPmFb7e22Q==", + "requested": "[1.9.0-alpha3, )", + "resolved": "1.9.0-alpha3", + "contentHash": "1czKqjx7dzU59Ilt0Ewp06aM5wsOgGS+CodRdWumhpyrxz8Hqnp3HCVDsBWhgElEPn0bkcdQFxG+W7xyJBXLkQ==", "dependencies": { - "ExcelDna.Integration": "[1.8.0]", + "ExcelDna.Integration": "[1.9.0-alpha3]", "ExcelDna.Interop": "[15.0.1]", "Microsoft.NET.Test.Sdk": "16.11.0", "Microsoft.VisualStudio.Interop": "17.1.32210.191", @@ -23,19 +23,19 @@ }, "xunit": { "type": "Direct", - "requested": "[2.9.0, )", - "resolved": "2.9.0", - "contentHash": "PtU3rZ0ThdmdJqTbK7GkgFf6iBaCR6Q0uvJHznID+XEYk2v6O/b7sRxqnbi3B2gRDXxjTqMkVNayzwsqsFUxRw==", + "requested": "[2.9.2, )", + "resolved": "2.9.2", + "contentHash": "7LhFS2N9Z6Xgg8aE5lY95cneYivRMfRI8v+4PATa4S64D5Z/Plkg0qa8dTRHSiGRgVZ/CL2gEfJDE5AUhOX+2Q==", "dependencies": { - "xunit.analyzers": "1.15.0", - "xunit.assert": "2.9.0", - "xunit.core": "[2.9.0]" + "xunit.analyzers": "1.16.0", + "xunit.assert": "2.9.2", + "xunit.core": "[2.9.2]" } }, "ExcelDna.Integration": { "type": "Transitive", - "resolved": "1.8.0", - "contentHash": "Z7UYuY291cTxc8lEESlUT73iI28yZ9LeJnvhPIRpZHBlrjbCN8lnEWvSmUqmUn1lBNGk6p2n0GlPuBhopJuksA==" + "resolved": "1.9.0-alpha3", + "contentHash": "vQZP+bXCXkiIx4bkntd2iWnMDS57ECcul2XFQvMSvgcTy6+2q81xI2Sp6FGXr9tU/t7eHfpG7vVv/7TZKOLnxA==" }, "ExcelDna.Interop": { "type": "Transitive", @@ -810,37 +810,37 @@ }, "xunit.analyzers": { "type": "Transitive", - "resolved": "1.15.0", - "contentHash": "s+M8K/Rtlgr6CmD7AYQKrNTvT5sh0l0ZKDoZ3Z/ExhlIwfV9mGAMR4f7KqIB7SSK7ZOhqDTgTUMYPmKfmvWUWQ==" + "resolved": "1.16.0", + "contentHash": "hptYM7vGr46GUIgZt21YHO4rfuBAQS2eINbFo16CV/Dqq+24Tp+P5gDCACu1AbFfW4Sp/WRfDPSK8fmUUb8s0Q==" }, "xunit.assert": { "type": "Transitive", - "resolved": "2.9.0", - "contentHash": "Z/1pyia//860wEYTKn6Q5dmgikJdRjgE4t5AoxJkK8oTmidzPLEPG574kmm7LFkMLbH6Frwmgb750kcyR+hwoA==" + "resolved": "2.9.2", + "contentHash": "QkNBAQG4pa66cholm28AxijBjrmki98/vsEh4Sx5iplzotvPgpiotcxqJQMRC8d7RV7nIT8ozh97957hDnZwsQ==" }, "xunit.core": { "type": "Transitive", - "resolved": "2.9.0", - "contentHash": "uRaop9tZsZMCaUS4AfbSPGYHtvywWnm8XXFNUqII7ShWyDBgdchY6gyDNgO4AK1Lv/1NNW61Zq63CsDV6oH6Jg==", + "resolved": "2.9.2", + "contentHash": "O6RrNSdmZ0xgEn5kT927PNwog5vxTtKrWMihhhrT0Sg9jQ7iBDciYOwzBgP2krBEk5/GBXI18R1lKvmnxGcb4w==", "dependencies": { - "xunit.extensibility.core": "[2.9.0]", - "xunit.extensibility.execution": "[2.9.0]" + "xunit.extensibility.core": "[2.9.2]", + "xunit.extensibility.execution": "[2.9.2]" } }, "xunit.extensibility.core": { "type": "Transitive", - "resolved": "2.9.0", - "contentHash": "zjDEUSxsr6UNij4gIwCgMqQox+oLDPRZ+mubwWLci+SssPBFQD1xeRR4SvgBuXqbE0QXCJ/STVTp+lxiB5NLVA==", + "resolved": "2.9.2", + "contentHash": "Ol+KlBJz1x8BrdnhN2DeOuLrr1I/cTwtHCggL9BvYqFuVd/TUSzxNT5O0NxCIXth30bsKxgMfdqLTcORtM52yQ==", "dependencies": { "xunit.abstractions": "2.0.3" } }, "xunit.extensibility.execution": { "type": "Transitive", - "resolved": "2.9.0", - "contentHash": "5ZTQZvmPLlBw6QzCOwM0KnMsZw6eGjbmC176QHZlcbQoMhGIeGcYzYwn5w9yXxf+4phtplMuVqTpTbFDQh2bqQ==", + "resolved": "2.9.2", + "contentHash": "rKMpq4GsIUIJibXuZoZ8lYp5EpROlnYaRpwu9Zr0sRZXE7JqJfEEbCsUriZqB+ByXCLFBJyjkTRULMdC+U566g==", "dependencies": { - "xunit.extensibility.core": "[2.9.0]" + "xunit.extensibility.core": "[2.9.2]" } }, "xunit.runner.visualstudio": { diff --git a/src/Cellm/Cellm.csproj b/src/Cellm/Cellm.csproj index 5c0642e..68a8562 100644 --- a/src/Cellm/Cellm.csproj +++ b/src/Cellm/Cellm.csproj @@ -18,30 +18,35 @@ - + - - - - + + + + - + - - - + + + + - + - - + + + + runtime + + Always diff --git a/src/Cellm/Models/Anthropic/AnthropicRequestHandler.cs b/src/Cellm/Models/Anthropic/AnthropicRequestHandler.cs index 378057d..bd67005 100644 --- a/src/Cellm/Models/Anthropic/AnthropicRequestHandler.cs +++ b/src/Cellm/Models/Anthropic/AnthropicRequestHandler.cs @@ -54,7 +54,7 @@ public string Serialize(AnthropicRequest request) var requestBody = new AnthropicRequestBody { System = request.Prompt.Messages.Where(x => x.Role == ChatRole.System).First().Text, - Messages = request.Prompt.Messages.Select(x => new AnthropicMessage { Content = x.Text, Role = x.Role.ToString().ToLower() }).ToList(), + Messages = request.Prompt.Messages.Where(x => x.Role != ChatRole.System).Select(x => new AnthropicMessage { Content = x.Text, Role = x.Role.ToString().ToLower() }).ToList(), Model = request.Prompt.Options.ModelId ?? _anthropicConfiguration.DefaultModel, MaxTokens = _cellmConfiguration.MaxOutputTokens, Temperature = request.Prompt.Options.Temperature ?? _cellmConfiguration.DefaultTemperature, diff --git a/src/Cellm/Models/Client.cs b/src/Cellm/Models/Client.cs index 0fb7482..fcf2124 100644 --- a/src/Cellm/Models/Client.cs +++ b/src/Cellm/Models/Client.cs @@ -38,8 +38,8 @@ public async Task Send(Prompt prompt, string? provider, Uri? baseAddress { Providers.Anthropic => await _sender.Send(new AnthropicRequest(prompt, provider, baseAddress)), Providers.Llamafile => await _sender.Send(new LlamafileRequest(prompt)), - Providers.Ollama => await _sender.Send(new OllamaRequest(prompt, provider, baseAddress)), - Providers.OpenAi => await _sender.Send(new OpenAiRequest(prompt, provider, baseAddress)), + Providers.Ollama => await _sender.Send(new OllamaRequest(prompt)), + Providers.OpenAi => await _sender.Send(new OpenAiRequest(prompt)), _ => throw new InvalidOperationException($"Provider {parsedProvider} is defined but not implemented") }; diff --git a/src/Cellm/Models/Llamafile/LlamafileRequestHandler.cs b/src/Cellm/Models/Llamafile/LlamafileRequestHandler.cs index dfacaff..6f54864 100644 --- a/src/Cellm/Models/Llamafile/LlamafileRequestHandler.cs +++ b/src/Cellm/Models/Llamafile/LlamafileRequestHandler.cs @@ -2,8 +2,9 @@ using Cellm.AddIn; using Cellm.AddIn.Exceptions; using Cellm.Models.Local; -using Cellm.Models.OpenAi; +using Cellm.Models.OpenAiCompatible; using MediatR; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace Cellm.Models.Llamafile; @@ -22,13 +23,15 @@ private record Llamafile(string ModelPath, Uri BaseAddress, Process Process); private readonly ISender _sender; private readonly HttpClient _httpClient; private readonly LocalUtilities _localUtilities; + private readonly ILogger _logger; public LlamafileRequestHandler(IOptions cellmConfiguration, IOptions llamafileConfiguration, ISender sender, HttpClient httpClient, LocalUtilities localUtilities, - ProcessManager processManager) + ProcessManager processManager, + ILogger logger) { _cellmConfiguration = cellmConfiguration.Value; _llamafileConfiguration = llamafileConfiguration.Value; @@ -36,21 +39,30 @@ public LlamafileRequestHandler(IOptions cellmConfiguration, _httpClient = httpClient; _localUtilities = localUtilities; _processManager = processManager; + _logger = logger; _llamafileExePath = new AsyncLazy(async () => { var llamafileName = Path.GetFileName(_llamafileConfiguration.LlamafileUrl.Segments.Last()); - return await _localUtilities.DownloadFile(_llamafileConfiguration.LlamafileUrl, $"{llamafileName}.exe"); + return await _localUtilities.DownloadFileIfNotExists(_llamafileConfiguration.LlamafileUrl, _localUtilities.CreateCellmFilePath(CreateModelFileName($"{llamafileName}.exe"), "Llamafile")); }); _llamafiles = _llamafileConfiguration.Models.ToDictionary(x => x.Key, x => new AsyncLazy(async () => { + // Download Llamafile + var exePath = await _llamafileExePath; + // Download model - var modelPath = await _localUtilities.DownloadFile(x.Value, _localUtilities.CreateCellmFilePath(CreateModelFileName(x.Key))); + var modelPath = await _localUtilities.DownloadFileIfNotExists(x.Value, _localUtilities.CreateCellmFilePath(CreateModelFileName(x.Key), "Llamafile")); // Start server - var baseAddress = new UriBuilder("http", "localhost", _localUtilities.FindPort()).Uri; - var process = await StartProcess(modelPath, baseAddress); + var baseAddress = new UriBuilder( + _llamafileConfiguration.BaseAddress.Scheme, + _llamafileConfiguration.BaseAddress.Host, + _localUtilities.FindPort(), + _llamafileConfiguration.BaseAddress.AbsolutePath).Uri; + + var process = await StartProcess(exePath, modelPath, baseAddress); return new Llamafile(modelPath, baseAddress, process); })); @@ -61,20 +73,23 @@ public async Task Handle(LlamafileRequest request, Cancellati // Start server on first call var llamafile = await _llamafiles[request.Prompt.Options.ModelId ?? _llamafileConfiguration.DefaultModel]; - var openAiResponse = await _sender.Send(new OpenAiRequest(request.Prompt, nameof(Llamafile), llamafile.BaseAddress), cancellationToken); + var openAiResponse = await _sender.Send(new OpenAiCompatibleRequest(request.Prompt, nameof(Llamafile), llamafile.BaseAddress), cancellationToken); return new LlamafileResponse(openAiResponse.Prompt); } - private async Task StartProcess(string modelPath, Uri baseAddress) + private async Task StartProcess(string exePath, string modelPath, Uri baseAddress) { - var processStartInfo = new ProcessStartInfo(await _llamafileExePath); + var processStartInfo = new ProcessStartInfo(exePath); - processStartInfo.Arguments += $"--server "; - processStartInfo.Arguments += "--nobrowser "; - processStartInfo.Arguments += $"-m {modelPath} "; - processStartInfo.Arguments += $"--host {baseAddress.Host} "; - processStartInfo.Arguments += $"--port {baseAddress.Port} "; + processStartInfo.ArgumentList.Add("--server"); + processStartInfo.ArgumentList.Add("--nobrowser"); + processStartInfo.ArgumentList.Add("-m"); + processStartInfo.ArgumentList.Add(modelPath); + processStartInfo.ArgumentList.Add("--host"); + processStartInfo.ArgumentList.Add(baseAddress.Host); + processStartInfo.ArgumentList.Add("--port"); + processStartInfo.ArgumentList.Add(baseAddress.Port.ToString()); if (_llamafileConfiguration.Gpu) { @@ -94,7 +109,7 @@ private async Task StartProcess(string modelPath, Uri baseAddress) { if (!string.IsNullOrEmpty(e.Data)) { - Debug.WriteLine(e.Data); + _logger.LogDebug(e.Data); } }; @@ -102,8 +117,8 @@ private async Task StartProcess(string modelPath, Uri baseAddress) process.BeginErrorReadLine(); } - var address = new Uri(baseAddress, "health"); - await _localUtilities.WaitForServer(address, process); + var uriBuilder = new UriBuilder(baseAddress.Scheme, baseAddress.Host, baseAddress.Port, "/health"); + await _localUtilities.WaitForServer(uriBuilder.Uri, process); // Kill Llamafile when Excel exits or dies _processManager.AssignProcessToExcel(process); @@ -113,7 +128,7 @@ private async Task StartProcess(string modelPath, Uri baseAddress) private static string CreateModelFileName(string modelName) { - return $"Llamafile-model-{modelName}"; + return $"Llamafile-{modelName}"; } } diff --git a/src/Cellm/Models/Local/LocalUtilities.cs b/src/Cellm/Models/Local/LocalUtilities.cs index 8194932..995141a 100644 --- a/src/Cellm/Models/Local/LocalUtilities.cs +++ b/src/Cellm/Models/Local/LocalUtilities.cs @@ -2,13 +2,12 @@ using System.IO.Compression; using System.Net.NetworkInformation; using Cellm.AddIn.Exceptions; -using Microsoft.Office.Interop.Excel; namespace Cellm.Models.Local; internal class LocalUtilities(HttpClient httpClient) { - public async Task DownloadFile(Uri uri, string filePath) + public async Task DownloadFileIfNotExists(Uri uri, string filePath) { if (File.Exists(filePath)) { @@ -37,22 +36,23 @@ public async Task DownloadFile(Uri uri, string filePath) return filePath; } - public async Task WaitForServer(Uri endpoint, Process process) + public async Task WaitForServer(Uri endpoint, Process process, int timeOutInSeconds = 30) { var startTime = DateTime.UtcNow; // Wait max 30 seconds to load model - while ((DateTime.UtcNow - startTime).TotalSeconds < 30) + while ((DateTime.UtcNow - startTime).TotalSeconds < timeOutInSeconds) { if (process.HasExited) { - throw new CellmException($"Failed to run Llamafile, process exited. Exit code: {process.ExitCode}"); + throw new CellmException($"Server not responding: {endpoint}"); } try { var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(1)); var response = await httpClient.GetAsync(endpoint, cancellationTokenSource.Token); + if (response.StatusCode == System.Net.HttpStatusCode.OK) { // Server is ready @@ -67,7 +67,7 @@ public async Task WaitForServer(Uri endpoint, Process process) } // Wait before next attempt - await Task.Delay(500); + await Task.Delay(100); } process.Kill(); @@ -88,9 +88,9 @@ public string CreateCellmDirectory(params string[] subFolders) return folderPath; } - public string CreateCellmFilePath(string fileName) + public string CreateCellmFilePath(string fileName, params string[] subFolders) { - return Path.Combine(CreateCellmDirectory(), fileName); + return Path.Combine(CreateCellmDirectory(subFolders), fileName); } public int FindPort(ushort min = 49152, ushort max = 65535) @@ -122,29 +122,15 @@ public int FindPort(ushort min = 49152, ushort max = 65535) return firstInactivePort; } - public string ExtractFile(string zipFilePath, string targetDirectory) + public string ExtractZipFileIfNotExtracted(string zipFilePath, string targetDirectory) { - using (ZipArchive archive = ZipFile.OpenRead(zipFilePath)) + if (Directory.Exists(targetDirectory)) { - foreach (ZipArchiveEntry entry in archive.Entries) - { - string destinationPath = Path.Combine(targetDirectory, entry.FullName); - - if (!File.Exists(destinationPath)) - { - ZipFile.ExtractToDirectory(zipFilePath, targetDirectory); - return targetDirectory; - } - - var fileInfo = new FileInfo(destinationPath); - if (fileInfo.Length != entry.Length) - { - ZipFile.ExtractToDirectory(zipFilePath, targetDirectory); - return targetDirectory; - } - } + return targetDirectory; } + ZipFile.ExtractToDirectory(zipFilePath, targetDirectory, true); + return targetDirectory; } } diff --git a/src/Cellm/Models/ModelRequestBehavior/ToolBehavior.cs b/src/Cellm/Models/ModelRequestBehavior/ToolBehavior.cs index 0eeec9f..f8d093d 100644 --- a/src/Cellm/Models/ModelRequestBehavior/ToolBehavior.cs +++ b/src/Cellm/Models/ModelRequestBehavior/ToolBehavior.cs @@ -6,22 +6,14 @@ namespace Cellm.Models.ModelRequestBehavior; -internal class ToolBehavior : IPipelineBehavior - where TRequest : IModelRequest +internal class ToolBehavior(IOptions cellmConfiguration, Functions functions) + : IPipelineBehavior where TRequest : IModelRequest { - private readonly CellmConfiguration _cellmConfiguration; - private readonly Functions _functions; - private readonly List _tools; - - public ToolBehavior(IOptions cellmConfiguration, Functions functions) - { - _cellmConfiguration = cellmConfiguration.Value; - _functions = functions; - _tools = [ - AIFunctionFactory.Create(_functions.GlobRequest), - AIFunctionFactory.Create(_functions.FileReaderRequest) - ]; - } + private readonly CellmConfiguration _cellmConfiguration = cellmConfiguration.Value; + private readonly List _tools = [ + AIFunctionFactory.Create(functions.GlobRequest), + AIFunctionFactory.Create(functions.FileReaderRequest) + ]; public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { @@ -30,8 +22,6 @@ public async Task Handle(TRequest request, RequestHandlerDelegate; +internal record OllamaRequest(Prompt Prompt) : IModelRequest; diff --git a/src/Cellm/Models/Ollama/OllamaRequestHandler.cs b/src/Cellm/Models/Ollama/OllamaRequestHandler.cs index 5e70725..ff5bd8a 100644 --- a/src/Cellm/Models/Ollama/OllamaRequestHandler.cs +++ b/src/Cellm/Models/Ollama/OllamaRequestHandler.cs @@ -1,26 +1,27 @@ using System.Diagnostics; -using System.IO.Compression; using System.Net.Http.Json; -using System.Runtime.InteropServices; using System.Text; using System.Text.Json; using Cellm.AddIn; using Cellm.AddIn.Exceptions; -using Cellm.Models.Llamafile; using Cellm.Models.Local; using Cellm.Prompts; using Microsoft.Extensions.AI; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Microsoft.Office.Interop.Excel; namespace Cellm.Models.Ollama; internal class OllamaRequestHandler : IModelRequestHandler { - private record Ollama(Uri BaseAddress, Process Process); + private record OllamaServer(Uri BaseAddress, Process Process); + + record Tags(List Models); record Model(string Name); + record Progress(string Status); + private readonly IChatClient _chatClient; private readonly CellmConfiguration _cellmConfiguration; private readonly OllamaConfiguration _ollamaConfiguration; private readonly HttpClient _httpClient; @@ -29,55 +30,73 @@ record Model(string Name); private readonly ILogger _logger; private readonly AsyncLazy _ollamaExePath; - private readonly AsyncLazy _ollama; + private readonly AsyncLazy _ollamaServer; public OllamaRequestHandler( + [FromKeyedServices(Providers.Ollama)] IChatClient chatClient, + IHttpClientFactory httpClientFactory, IOptions cellmConfiguration, IOptions ollamaConfiguration, - HttpClient httpClient, LocalUtilities localUtilities, ProcessManager processManager, ILogger logger) { + _chatClient = chatClient; + _httpClient = httpClientFactory.CreateClient(nameof(Providers.Ollama)); _cellmConfiguration = cellmConfiguration.Value; _ollamaConfiguration = ollamaConfiguration.Value; - _httpClient = httpClient; _localUtilities = localUtilities; _processManager = processManager; _logger = logger; _ollamaExePath = new AsyncLazy(async () => { - var zipFileName = string.Join("-", _ollamaConfiguration.OllamaUri.Segments.TakeLast(2)); + var zipFileName = string.Join("-", _ollamaConfiguration.ZipUrl.Segments.Select(x => x.Replace("/", string.Empty)).TakeLast(2)); var zipFilePath = _localUtilities.CreateCellmFilePath(zipFileName); - await _localUtilities.DownloadFile(_ollamaConfiguration.OllamaUri, zipFilePath); - var ollamaPath = _localUtilities.ExtractFile(zipFilePath, _localUtilities.CreateCellmDirectory(nameof(Ollama), Path.GetFileNameWithoutExtension(zipFileName))); + await _localUtilities.DownloadFileIfNotExists( + _ollamaConfiguration.ZipUrl, + zipFilePath); + + var ollamaPath = _localUtilities.ExtractZipFileIfNotExtracted( + zipFilePath, + _localUtilities.CreateCellmDirectory(nameof(Ollama), Path.GetFileNameWithoutExtension(zipFileName))); + return Path.Combine(ollamaPath, "ollama.exe"); }); - _ollama = new AsyncLazy(async () => + _ollamaServer = new AsyncLazy(async () => { - var baseAddress = new UriBuilder("http", "localhost", _localUtilities.FindPort()).Uri; - var process = await StartProcess(baseAddress); + var ollamaExePath = await _ollamaExePath; + var process = await StartProcess(ollamaExePath, _ollamaConfiguration.BaseAddress); - return new Ollama(baseAddress, process); + return new OllamaServer(_ollamaConfiguration.BaseAddress, process); }); } public async Task Handle(OllamaRequest request, CancellationToken cancellationToken) { - // Start server on first call - _ = await _ollama; + var serverIsRunning = await ServerIsRunning(_ollamaConfiguration.BaseAddress); + if (_ollamaConfiguration.EnableServer && !serverIsRunning) + { + _ = await _ollamaServer; + } - var modelId = request.Prompt.Options.ModelId ?? _ollamaConfiguration.DefaultModel; + var modelIsDownloaded = await ModelIsDownloaded( + _ollamaConfiguration.BaseAddress, + request.Prompt.Options.ModelId ?? _ollamaConfiguration.DefaultModel); - const string path = "/v1/chat/completions"; - var address = request.BaseAddress is null ? new Uri(path, UriKind.Relative) : new Uri(request.BaseAddress, path); + if (!modelIsDownloaded) + { + await DownloadModel( + _ollamaConfiguration.BaseAddress, + request.Prompt.Options.ModelId ?? _ollamaConfiguration.DefaultModel); + } - // Must instantiate manually because address can be set/changed only at instantiation - var chatClient = await GetChatClient(address, modelId); - var chatCompletion = await chatClient.CompleteAsync(request.Prompt.Messages, request.Prompt.Options, cancellationToken); + var chatCompletion = await _chatClient.CompleteAsync( + request.Prompt.Messages, + request.Prompt.Options, + cancellationToken); var prompt = new PromptBuilder(request.Prompt) .AddMessage(chatCompletion.Message) @@ -86,11 +105,48 @@ public async Task Handle(OllamaRequest request, CancellationToke return new OllamaResponse(prompt); } - private async Task StartProcess(Uri baseAddress) + private async Task ServerIsRunning(Uri baseAddress) + { + var response = await _httpClient.GetAsync(baseAddress); + + return response.IsSuccessStatusCode; + } + + private async Task ModelIsDownloaded(Uri baseAddress, string modelId) + { + var tags = await _httpClient.GetFromJsonAsync("api/tags") ?? throw new CellmException(); + + return tags.Models.Select(x => x.Name).Contains(modelId); + } + + private async Task DownloadModel(Uri baseAddress, string modelId) + { + try + { + var modelName = JsonSerializer.Serialize(new { name = modelId }); + var modelStringContent = new StringContent(modelName, Encoding.UTF8, "application/json"); + var response = await _httpClient.PostAsync("api/pull", modelStringContent); + + response.EnsureSuccessStatusCode(); + + var progress = await response.Content.ReadFromJsonAsync>(); + + if (progress is null || progress.Last().Status != "success") + { + throw new CellmException($"Ollama failed to download model {modelId}"); + } + } + catch (HttpRequestException ex) + { + throw new CellmException($"Ollama failed to download model {modelId} or {modelId} does not exist", ex); + } + } + + private async Task StartProcess(string ollamaExePath, Uri baseAddress) { var processStartInfo = new ProcessStartInfo(await _ollamaExePath); - processStartInfo.Arguments += $"serve "; + processStartInfo.ArgumentList.Add("serve"); processStartInfo.EnvironmentVariables.Add("OLLAMA_HOST", baseAddress.ToString()); processStartInfo.UseShellExecute = false; @@ -123,22 +179,4 @@ private async Task StartProcess(Uri baseAddress) return process; } - - private async Task GetChatClient(Uri address, string modelId) - { - // Download model if it doesn't exist - var models = await _httpClient.GetFromJsonAsync>("api/tags") ?? throw new CellmException(); - - if (!models.Select(x => x.Name).Contains(modelId)) - { - var body = new StringContent($"{{\"model\":\"{modelId}\", \"stream\": \"false\"}}", Encoding.UTF8, "application/json"); - var response = await _httpClient.PostAsync("api/pull", body); - response.EnsureSuccessStatusCode(); - } - - return new ChatClientBuilder() - .UseLogging() - .UseFunctionInvocation() - .Use(new OllamaChatClient(address, modelId, _httpClient)); - } } diff --git a/src/Cellm/Models/Ollama/ServiceCollectionExtensions.cs b/src/Cellm/Models/Ollama/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..f89556e --- /dev/null +++ b/src/Cellm/Models/Ollama/ServiceCollectionExtensions.cs @@ -0,0 +1,38 @@ +using Cellm.Services.Configuration; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Cellm.Models.Ollama; + +internal static class ServiceCollectionExtensions +{ + public static IServiceCollection AddOpenOllamaChatClient(this IServiceCollection services, IConfiguration configuration) + { + var resiliencePipelineConfigurator = new ResiliencePipelineConfigurator(configuration); + + var ollamaConfiguration = configuration.GetRequiredSection(nameof(OllamaConfiguration)).Get() + ?? throw new NullReferenceException(nameof(OllamaConfiguration)); + + services + .AddHttpClient(nameof(Providers.Ollama), ollamaHttpClient => + { + ollamaHttpClient.BaseAddress = ollamaConfiguration.BaseAddress; + ollamaHttpClient.Timeout = TimeSpan.FromHours(1); + }) + .AddResilienceHandler( + $"{nameof(OllamaRequestHandler)}ResiliencePipeline", + resiliencePipelineConfigurator.ConfigureResiliencePipeline); + + services + .AddKeyedChatClient(Providers.Ollama, serviceProvider => new OllamaChatClient( + ollamaConfiguration.BaseAddress, + ollamaConfiguration.DefaultModel, + serviceProvider + .GetRequiredService() + .CreateClient(nameof(Providers.Ollama)))) + .UseFunctionInvocation(); + + return services; + } +} diff --git a/src/Cellm/Models/OpenAi/OpenAiConfiguration.cs b/src/Cellm/Models/OpenAi/OpenAiConfiguration.cs index 3569856..e963d98 100644 --- a/src/Cellm/Models/OpenAi/OpenAiConfiguration.cs +++ b/src/Cellm/Models/OpenAi/OpenAiConfiguration.cs @@ -1,19 +1,8 @@ -using Cellm.Services.Configuration; +namespace Cellm.Models.OpenAi; -namespace Cellm.Models.OpenAi; - -internal class OpenAiConfiguration : IProviderConfiguration +internal class OpenAiConfiguration { - public Uri BaseAddress { get; init; } - - public string DefaultModel { get; init; } - - public string ApiKey { get; init; } + public string DefaultModel { get; init; } = string.Empty; - public OpenAiConfiguration() - { - BaseAddress = default!; - DefaultModel = default!; - ApiKey = default!; - } + public string ApiKey { get; init; } = string.Empty; } diff --git a/src/Cellm/Models/OpenAi/OpenAiRequest.cs b/src/Cellm/Models/OpenAi/OpenAiRequest.cs index ab4653c..88e314d 100644 --- a/src/Cellm/Models/OpenAi/OpenAiRequest.cs +++ b/src/Cellm/Models/OpenAi/OpenAiRequest.cs @@ -2,4 +2,4 @@ namespace Cellm.Models.OpenAi; -internal record OpenAiRequest(Prompt Prompt, string? Provider, Uri? BaseAddress) : IModelRequest; +internal record OpenAiRequest(Prompt Prompt) : IModelRequest; diff --git a/src/Cellm/Models/OpenAi/OpenAiRequestHandler.cs b/src/Cellm/Models/OpenAi/OpenAiRequestHandler.cs index 8016470..76c6c8d 100644 --- a/src/Cellm/Models/OpenAi/OpenAiRequestHandler.cs +++ b/src/Cellm/Models/OpenAi/OpenAiRequestHandler.cs @@ -1,25 +1,14 @@ -using System.ClientModel; -using System.ClientModel.Primitives; -using Cellm.Prompts; +using Cellm.Prompts; using Microsoft.Extensions.AI; -using Microsoft.Extensions.Options; -using OpenAI; +using Microsoft.Extensions.DependencyInjection; namespace Cellm.Models.OpenAi; -internal class OpenAiRequestHandler(IOptions openAiConfiguration, HttpClient httpClient) : IModelRequestHandler +internal class OpenAiRequestHandler([FromKeyedServices(Providers.OpenAi)] IChatClient chatClient) : IModelRequestHandler { - private readonly OpenAiConfiguration _openAiConfiguration = openAiConfiguration.Value; public async Task Handle(OpenAiRequest request, CancellationToken cancellationToken) { - var modelId = request.Prompt.Options.ModelId ?? _openAiConfiguration.DefaultModel; - - const string path = "/v1/chat/completions"; - var address = request.BaseAddress is null ? new Uri(path, UriKind.Relative) : new Uri(request.BaseAddress, path); - - // Must instantiate manually because address can be set/changed only at instantiation - var chatClient = GetChatClient(address, modelId); var chatCompletion = await chatClient.CompleteAsync(request.Prompt.Messages, request.Prompt.Options, cancellationToken); var prompt = new PromptBuilder(request.Prompt) @@ -28,21 +17,4 @@ public async Task Handle(OpenAiRequest request, CancellationToke return new OpenAiResponse(prompt); } - - private IChatClient GetChatClient(Uri address, string modelId) - { - var openAiClientCredentials = new ApiKeyCredential(_openAiConfiguration.ApiKey); - var openAiClientOptions = new OpenAIClientOptions - { - Transport = new HttpClientPipelineTransport(httpClient), - Endpoint = address - }; - - var openAiClient = new OpenAIClient(openAiClientCredentials, openAiClientOptions); - - return new ChatClientBuilder() - .UseLogging() - .UseFunctionInvocation() - .Use(openAiClient.AsChatClient(modelId)); - } } diff --git a/src/Cellm/Models/OpenAi/ServiceCollectionExtensions.cs b/src/Cellm/Models/OpenAi/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..2ad5af9 --- /dev/null +++ b/src/Cellm/Models/OpenAi/ServiceCollectionExtensions.cs @@ -0,0 +1,21 @@ +using Microsoft.Extensions.AI; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using OpenAI; + +namespace Cellm.Models.OpenAi; + +internal static class ServiceCollectionExtensions +{ + public static IServiceCollection AddOpenAiChatClient(this IServiceCollection services, IConfiguration configuration) + { + var openAiConfiguration = configuration.GetRequiredSection(nameof(OpenAiConfiguration)).Get() + ?? throw new NullReferenceException(nameof(OpenAiConfiguration)); + + services + .AddKeyedChatClient(Providers.OpenAi, new OpenAIClient(openAiConfiguration.ApiKey).AsChatClient(openAiConfiguration.DefaultModel)) + .UseFunctionInvocation(); + + return services; + } +} diff --git a/src/Cellm/Models/OpenAiCompatible/OpenAiCompatibleRequest.cs b/src/Cellm/Models/OpenAiCompatible/OpenAiCompatibleRequest.cs new file mode 100644 index 0000000..da2e0c6 --- /dev/null +++ b/src/Cellm/Models/OpenAiCompatible/OpenAiCompatibleRequest.cs @@ -0,0 +1,5 @@ +using Cellm.Prompts; + +namespace Cellm.Models.OpenAiCompatible; + +internal record OpenAiCompatibleRequest(Prompt Prompt, string Provider, Uri BaseAddress) : IModelRequest; diff --git a/src/Cellm/Models/OpenAiCompatible/OpenAiCompatibleRequestHandler.cs b/src/Cellm/Models/OpenAiCompatible/OpenAiCompatibleRequestHandler.cs new file mode 100644 index 0000000..c189b01 --- /dev/null +++ b/src/Cellm/Models/OpenAiCompatible/OpenAiCompatibleRequestHandler.cs @@ -0,0 +1,10 @@ + +namespace Cellm.Models.OpenAiCompatible; + +internal class OpenAiCompatibleRequestHandler : IModelRequestHandler +{ + public Task Handle(OpenAiCompatibleRequest request, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } +} diff --git a/src/Cellm/Models/OpenAiCompatible/OpenAiCompatibleResponse.cs b/src/Cellm/Models/OpenAiCompatible/OpenAiCompatibleResponse.cs new file mode 100644 index 0000000..fe735cd --- /dev/null +++ b/src/Cellm/Models/OpenAiCompatible/OpenAiCompatibleResponse.cs @@ -0,0 +1,5 @@ +using Cellm.Prompts; + +namespace Cellm.Models.OpenAiCompatible; + +internal record OpenAiCompatibleResponse(Prompt Prompt) : IModelResponse; diff --git a/src/Cellm/Models/OpenAiCompatible/SerrviceCollectionExtensions.cs b/src/Cellm/Models/OpenAiCompatible/SerrviceCollectionExtensions.cs new file mode 100644 index 0000000..d7b8e64 --- /dev/null +++ b/src/Cellm/Models/OpenAiCompatible/SerrviceCollectionExtensions.cs @@ -0,0 +1,44 @@ +using Cellm.Services.Configuration; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Cellm.Models.OpenAiCompatible; + +internal static class ServiceCollectionExtensions +{ + public static IServiceCollection AddOpenAiCompatibleChatClient(this IServiceCollection services, string provider, IConfiguration configuration) + { + var resiliencePipelineConfigurator = new ResiliencePipelineConfigurator(configuration); + + services + .AddHttpClient(provider, openAiCompatibleHttpClient => + { + openAiCompatibleHttpClient.Timeout = TimeSpan.FromHours(1); + }) + .AddResilienceHandler($"{nameof(OpenAiCompatibleRequestHandler)}ResiliencePipeline", resiliencePipelineConfigurator.ConfigureResiliencePipeline); + + // This is probably not needed, because we would send a OpenAiCompatibleRequestHandler(Prompt prompt, Uri BaseAddress) and instantiate a client on each call + //var openAiCompatibleConfiguration = configuration.GetRequiredSection($"{provider}Configuration").Get() + // ?? throw new NullReferenceException(nameof(provider)); + + //services + // .AddKeyedChatClient(Providers.OpenAiCompatible, serviceProvider => + // { + // var openAiCompatibleHttpClient = serviceProvider + // .GetRequiredService() + // .CreateClient(provider); + + // var openAiClient = new OpenAIClient( + // new ApiKeyCredential(openAiCompatibleConfiguration.ApiKey), + // new OpenAIClientOptions + // { + // Transport = new HttpClientPipelineTransport(openAiCompatibleHttpClient), + // }); + + // return openAiClient.AsChatClient(openAiCompatibleConfiguration.DefaultModel); + // }) + // .UseFunctionInvocation(); + + return services; + } +} diff --git a/src/Cellm/Models/Providers.cs b/src/Cellm/Models/Providers.cs index 9f30d8d..f9a590b 100644 --- a/src/Cellm/Models/Providers.cs +++ b/src/Cellm/Models/Providers.cs @@ -5,5 +5,6 @@ public enum Providers Anthropic, Llamafile, Ollama, - OpenAi + OpenAi, + OpenAiCompatible } diff --git a/src/Cellm/Services/Configuration/ResiliencePipelineConfigurator.cs b/src/Cellm/Services/Configuration/ResiliencePipelineConfigurator.cs index 173e63e..cb01908 100644 --- a/src/Cellm/Services/Configuration/ResiliencePipelineConfigurator.cs +++ b/src/Cellm/Services/Configuration/ResiliencePipelineConfigurator.cs @@ -1,6 +1,7 @@ using System.Net; using System.Threading.RateLimiting; using Cellm.AddIn; +using Microsoft.Extensions.Configuration; using Polly; using Polly.CircuitBreaker; using Polly.Retry; @@ -15,16 +16,19 @@ public class ResiliencePipelineConfigurator private readonly CircuitBreakerConfiguration _circuitBreakerConfiguration; private readonly RetryConfiguration _retryConfiguration; - public ResiliencePipelineConfigurator( - CellmConfiguration cellmConfiguration, - RateLimiterConfiguration rateLimiterConfiguration, - CircuitBreakerConfiguration circuitBreakerConfiguration, - RetryConfiguration retryConfiguration) + public ResiliencePipelineConfigurator(IConfiguration configuration) { - _cellmConfiguration = cellmConfiguration; - _rateLimiterConfiguration = rateLimiterConfiguration; - _circuitBreakerConfiguration = circuitBreakerConfiguration; - _retryConfiguration = retryConfiguration; + _cellmConfiguration = configuration.GetRequiredSection(nameof(CellmConfiguration)).Get() + ?? throw new NullReferenceException(nameof(CellmConfiguration)); + + _rateLimiterConfiguration = configuration.GetRequiredSection(nameof(RateLimiterConfiguration)).Get() + ?? throw new NullReferenceException(nameof(RateLimiterConfiguration)); + + _circuitBreakerConfiguration = configuration.GetRequiredSection(nameof(CircuitBreakerConfiguration)).Get() + ?? throw new NullReferenceException(nameof(CircuitBreakerConfiguration)); + + _retryConfiguration = configuration.GetRequiredSection(nameof(RetryConfiguration)).Get() + ?? throw new NullReferenceException(nameof(RetryConfiguration)); } public void ConfigureResiliencePipeline(ResiliencePipelineBuilder builder) diff --git a/src/Cellm/Services/ServiceLocator.cs b/src/Cellm/Services/ServiceLocator.cs index 9ac7180..2c56f81 100644 --- a/src/Cellm/Services/ServiceLocator.cs +++ b/src/Cellm/Services/ServiceLocator.cs @@ -9,6 +9,7 @@ using Cellm.Models.Ollama; using Cellm.Models.OpenAi; using Cellm.Services.Configuration; +using Cellm.Tools; using Cellm.Tools.FileReader; using ExcelDna.Integration; using MediatR; @@ -86,12 +87,12 @@ private static IServiceCollection ConfigureServices(IServiceCollection services) // Internals services .AddSingleton(configuration) - .AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly())) + .AddMediatR(mediatrConfiguration => mediatrConfiguration.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly())) .AddTransient() .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(); ; + .AddSingleton(); #pragma warning disable EXTEXP0018 // Type is for evaluation purposes only and is subject to change or removal in future updates. services @@ -100,52 +101,31 @@ private static IServiceCollection ConfigureServices(IServiceCollection services) // Tools services + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton(); // Model Providers - var rateLimiterConfiguration = configuration.GetRequiredSection(nameof(RateLimiterConfiguration)).Get() - ?? throw new NullReferenceException(nameof(RateLimiterConfiguration)); - var circuitBreakerConfiguration = configuration.GetRequiredSection(nameof(CircuitBreakerConfiguration)).Get() - ?? throw new NullReferenceException(nameof(CircuitBreakerConfiguration)); - - var retryConfiguration = configuration.GetRequiredSection(nameof(RetryConfiguration)).Get() - ?? throw new NullReferenceException(nameof(RetryConfiguration)); - - var resiliencePipelineConfigurator = new ResiliencePipelineConfigurator( - cellmConfiguration, rateLimiterConfiguration, circuitBreakerConfiguration, retryConfiguration); + var resiliencePipelineConfigurator = new ResiliencePipelineConfigurator(configuration); var anthropicConfiguration = configuration.GetRequiredSection(nameof(AnthropicConfiguration)).Get() ?? throw new NullReferenceException(nameof(AnthropicConfiguration)); - services.AddHttpClient, AnthropicRequestHandler>(anthropicHttpClient => - { - anthropicHttpClient.BaseAddress = anthropicConfiguration.BaseAddress; - anthropicHttpClient.DefaultRequestHeaders.Add("x-api-key", anthropicConfiguration.ApiKey); - anthropicHttpClient.DefaultRequestHeaders.Add("anthropic-version", anthropicConfiguration.Version); - anthropicHttpClient.Timeout = TimeSpan.FromHours(1); - }).AddResilienceHandler($"{nameof(AnthropicRequestHandler)}ResiliencePipeline", resiliencePipelineConfigurator.ConfigureResiliencePipeline); - - var ollamaConfiguration = configuration.GetRequiredSection(nameof(OllamaConfiguration)).Get() - ?? throw new NullReferenceException(nameof(OllamaConfiguration)); - - services.AddHttpClient, OllamaRequestHandler>(ollamaHttpClient => - { - ollamaHttpClient.BaseAddress = ollamaConfiguration.BaseAddress; - ollamaHttpClient.Timeout = TimeSpan.FromHours(1); - }).AddResilienceHandler($"{nameof(OllamaRequestHandler)}ResiliencePipeline", resiliencePipelineConfigurator.ConfigureResiliencePipeline); - - var openAiConfiguration = configuration.GetRequiredSection(nameof(OpenAiConfiguration)).Get() - ?? throw new NullReferenceException(nameof(OpenAiConfiguration)); + services + .AddHttpClient, AnthropicRequestHandler>(anthropicHttpClient => + { + anthropicHttpClient.BaseAddress = anthropicConfiguration.BaseAddress; + anthropicHttpClient.DefaultRequestHeaders.Add("x-api-key", anthropicConfiguration.ApiKey); + anthropicHttpClient.DefaultRequestHeaders.Add("anthropic-version", anthropicConfiguration.Version); + anthropicHttpClient.Timeout = TimeSpan.FromHours(1); + }) + .AddResilienceHandler($"{nameof(AnthropicRequestHandler)}ResiliencePipeline", resiliencePipelineConfigurator.ConfigureResiliencePipeline); - services.AddHttpClient, OpenAiRequestHandler>(openAiHttpClient => - { - openAiHttpClient.BaseAddress = openAiConfiguration.BaseAddress; - openAiHttpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {openAiConfiguration.ApiKey}"); - openAiHttpClient.Timeout = TimeSpan.FromHours(1); - }).AddResilienceHandler($"{nameof(OpenAiRequestHandler)}ResiliencePipeline", resiliencePipelineConfigurator.ConfigureResiliencePipeline); + services + .AddOpenOllamaChatClient(configuration) + .AddOpenAiChatClient(configuration); // Model request pipeline services diff --git a/src/Cellm/appsettings.Local.Google.json b/src/Cellm/appsettings.Local.Google.json index 4cb1770..7acb86f 100644 --- a/src/Cellm/appsettings.Local.Google.json +++ b/src/Cellm/appsettings.Local.Google.json @@ -1,12 +1,12 @@ { - "OpenAiConfiguration": { - "BaseAddress": "https://generativelanguage.googleapis.com/v1beta/openai/", + "OpenAiCompatibleConfiguration": { + "BaseAddress": "https://generativelanguage.googleapis.com/v1beta/openai/v1", "DefaultModel": "gemini-1.5-flash", "ApiKey": "YOUR_GEMINI_API_KEY" }, "CellmConfiguration": { - "DefaultProvider": "OpenAi", + "DefaultProvider": "OpenAiCompatible", "HttpTimeoutInSeconds": 30 } } diff --git a/src/Cellm/appsettings.Local.Llamafile.GPU.json b/src/Cellm/appsettings.Local.Llamafile.GPU.json index 2c84d7a..a7c71fd 100644 --- a/src/Cellm/appsettings.Local.Llamafile.GPU.json +++ b/src/Cellm/appsettings.Local.Llamafile.GPU.json @@ -1,15 +1,5 @@ { "LlamafileConfiguration": { - "LlamafileUrl": "https://github.com/Mozilla-Ocho/llamafile/releases/download/0.8.13/llamafile-0.8.13", - "BaseAddress": "http://127.0.0.1", - "DefaultModel": "gemma-2-2b", - "Models": { - "gemma-2-2b": "https://huggingface.co/bartowski/gemma-2-2b-it-GGUF/resolve/main/gemma-2-2b-it-Q6_K.gguf", - "gemma-2-9b": "https://huggingface.co/bartowski/gemma-2-9b-it-GGUF/resolve/main/gemma-2-9b-it-Q4_K_L.gguf", - "llama-3.2-1b": "https://huggingface.co/bartowski/Llama-3.2-1B-Instruct-GGUF/resolve/main/Llama-3.2-1B-Instruct-Q6_K_L.gguf", - "llama-3.2-3b": "https://huggingface.co/bartowski/Llama-3.2-3B-Instruct-GGUF/resolve/main/Llama-3.2-3B-Instruct-Q4_K_L.gguf", - "ministral-8b": "https://huggingface.co/bartowski/Ministral-8B-Instruct-2410-GGUF/resolve/main/Ministral-8B-Instruct-2410-Q5_K_L.gguf" - }, "GPU": true, "GpuLayers": 999 }, diff --git a/src/Cellm/appsettings.Local.Llamafile.json b/src/Cellm/appsettings.Local.Llamafile.json index c6474fb..e3de534 100644 --- a/src/Cellm/appsettings.Local.Llamafile.json +++ b/src/Cellm/appsettings.Local.Llamafile.json @@ -1,16 +1,4 @@ { - "LlamafileConfiguration": { - "LlamafileUrl": "https://github.com/Mozilla-Ocho/llamafile/releases/download/0.8.16/llamafile-0.8.16", - "BaseAddress": "http://127.0.0.1", - "DefaultModel": "gemma-2-2b", - "Models": { - "gemma-2-2b": "https://huggingface.co/bartowski/gemma-2-2b-it-GGUF/resolve/main/gemma-2-2b-it-Q6_K.gguf", - "gemma-2-9b": "https://huggingface.co/bartowski/gemma-2-9b-it-GGUF/resolve/main/gemma-2-9b-it-Q4_K_L.gguf", - "llama-3.2-1b": "https://huggingface.co/bartowski/Llama-3.2-1B-Instruct-GGUF/resolve/main/Llama-3.2-1B-Instruct-Q6_K_L.gguf", - "llama-3.2-3b": "https://huggingface.co/bartowski/Llama-3.2-3B-Instruct-GGUF/resolve/main/Llama-3.2-3B-Instruct-Q4_K_L.gguf", - "ministral-8b": "https://huggingface.co/bartowski/Ministral-8B-Instruct-2410-GGUF/resolve/main/Ministral-8B-Instruct-2410-Q5_K_L.gguf" - } - }, "CellmConfiguration": { "DefaultProvider": "Llamafile" } diff --git a/src/Cellm/appsettings.Local.Mistral.json b/src/Cellm/appsettings.Local.Mistral.json index 4a64398..1ad62ff 100644 --- a/src/Cellm/appsettings.Local.Mistral.json +++ b/src/Cellm/appsettings.Local.Mistral.json @@ -1,11 +1,11 @@ { - "OpenAiConfiguration": { + "OpenAiCompatibleConfiguration": { "BaseAddress": "https://api.mistral.ai", "DefaultModel": "mistral-small-latest", "ApiKey": "YOUR_MISTRAL_API_KEY" }, "CellmConfiguration": { - "DefaultProvider": "OpenAI", + "DefaultProvider": "OpenAiCompatible", "HttpTimeoutInSeconds": 30 } } diff --git a/src/Cellm/appsettings.Local.vLLM.json b/src/Cellm/appsettings.Local.vLLM.json index af499da..8362101 100644 --- a/src/Cellm/appsettings.Local.vLLM.json +++ b/src/Cellm/appsettings.Local.vLLM.json @@ -1,8 +1,8 @@ { - "OpenAiConfiguration": { + "OpenAiCompatibleConfiguration": { "BaseAddress": "http://localhost:8000" }, "CellmConfiguration": { - "DefaultProvider": "OpenAI" + "DefaultProvider": "OpenAiCompatible" } } diff --git a/src/Cellm/appsettings.json b/src/Cellm/appsettings.json index 42cb4c1..b046333 100644 --- a/src/Cellm/appsettings.json +++ b/src/Cellm/appsettings.json @@ -4,13 +4,26 @@ "DefaultModel": "claude-3-5-sonnet-latest", "Version": "2023-06-01" }, + "LlamafileConfiguration": { + "LlamafileUrl": "https://github.com/Mozilla-Ocho/llamafile/releases/download/0.8.16/llamafile-0.8.16", + "BaseAddress": "http://127.0.0.1/v1", + "DefaultModel": "gemma-2-2b", + "Models": { + "gemma-2-2b": "https://huggingface.co/bartowski/gemma-2-2b-it-GGUF/resolve/main/gemma-2-2b-it-Q6_K.gguf", + "gemma-2-9b": "https://huggingface.co/bartowski/gemma-2-9b-it-GGUF/resolve/main/gemma-2-9b-it-Q4_K_L.gguf", + "llama-3.2-1b": "https://huggingface.co/bartowski/Llama-3.2-1B-Instruct-GGUF/resolve/main/Llama-3.2-1B-Instruct-Q6_K_L.gguf", + "llama-3.2-3b": "https://huggingface.co/bartowski/Llama-3.2-3B-Instruct-GGUF/resolve/main/Llama-3.2-3B-Instruct-Q4_K_L.gguf", + "ministral-8b": "https://huggingface.co/bartowski/Ministral-8B-Instruct-2410-GGUF/resolve/main/Ministral-8B-Instruct-2410-Q5_K_L.gguf" + } + }, "OllamaConfiguration": { - "OllamaUri": "https://github.com/ollama/ollama/releases/download/v0.4.2/ollama-windows-amd64.zip", - "BaseAddress": "http://localhost:11434", - "DefaultModel": "gemma-2:2b" + "ZipUrl": "https://github.com/ollama/ollama/releases/download/v0.4.2/ollama-windows-amd64.zip", + "BaseAddress": "http://127.0.0.1:11434", + "DefaultModel": "gemma2:2b", + "EnableServer": true }, "OpenAiConfiguration": { - "BaseAddress": "https://api.openai.com", + "BaseAddress": "https://api.openai.com/v1", "DefaultModel": "gpt-4o-mini" }, "CellmConfiguration": { diff --git a/src/Cellm/packages.lock.json b/src/Cellm/packages.lock.json index ead687e..0ade8f7 100644 --- a/src/Cellm/packages.lock.json +++ b/src/Cellm/packages.lock.json @@ -4,11 +4,11 @@ "net8.0-windows7.0": { "ExcelDna.AddIn": { "type": "Direct", - "requested": "[1.8.0, )", - "resolved": "1.8.0", - "contentHash": "Q+NcNeuzIkE6d21Sc0NGBpfKClKO8QU2ozR/EZ9RE+WjCxwwOUWXPRLev8+DpOCwkUjXtBIKjE7+xxHTac3PHA==", + "requested": "[1.9.0-alpha3, )", + "resolved": "1.9.0-alpha3", + "contentHash": "N77/NrKCNQTjoVrNKFyYIZIPxVTLEbkoU7B0lLGuEbfFQLzBZ/CAEfQDSRYB0qo47os1yPAbE7y+LWTRR3S4Bw==", "dependencies": { - "ExcelDna.Integration": "[1.8.0]" + "ExcelDna.Integration": "[1.9.0-alpha3]" } }, "ExcelDna.Interop": { @@ -48,43 +48,44 @@ }, "Microsoft.Extensions.AI": { "type": "Direct", - "requested": "[9.0.0-preview.9.24556.5, )", - "resolved": "9.0.0-preview.9.24556.5", - "contentHash": "71WuoNoQObf3OVWFMuGt7WB/iLlHxobH7aUV7yKVO3/p2+JbAOXXyzcD4EoLsA/40HYZmDSXVKAzIZ6co+0vNg==", + "requested": "[9.0.1-preview.1.24570.5, )", + "resolved": "9.0.1-preview.1.24570.5", + "contentHash": "J38WKNCFGAFhFrUPSHfgaRl593/guj9jAIvD7LS9VUdP3kh15kTSA2tJgQFubWpItPnmESDLtkggOEFu6AKEYw==", "dependencies": { - "Microsoft.Extensions.AI.Abstractions": "9.0.0-preview.9.24556.5", + "Microsoft.Extensions.AI.Abstractions": "9.0.1-preview.1.24570.5", "Microsoft.Extensions.Caching.Abstractions": "9.0.0", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", "Microsoft.Extensions.Logging.Abstractions": "9.0.0", "System.Diagnostics.DiagnosticSource": "9.0.0", - "System.Text.Json": "9.0.0" + "System.Text.Json": "9.0.0", + "System.Threading.Channels": "9.0.0" } }, "Microsoft.Extensions.AI.Abstractions": { "type": "Direct", - "requested": "[9.0.0-preview.9.24556.5, )", - "resolved": "9.0.0-preview.9.24556.5", - "contentHash": "h+sY5wkFV7FCFN4KURGiC2yTwXysLtFCZW/ndx9urCMfRyHt+2Z3YW3NBGjXYbAMCB+BQbkzc2aQ9SLKCCzgVA==" + "requested": "[9.0.1-preview.1.24570.5, )", + "resolved": "9.0.1-preview.1.24570.5", + "contentHash": "zqUK1epXT+mxmJW7bonO2gRuYEhysLGIGCbPVXW5qmgYA+8PgFle9IPeKpb9RevwxnEedXdHm+nQyQiOnZIHWg==" }, "Microsoft.Extensions.AI.Ollama": { "type": "Direct", - "requested": "[9.0.0-preview.9.24556.5, )", - "resolved": "9.0.0-preview.9.24556.5", - "contentHash": "Wsn1LjuI6Z3opqPHauvNgTAt/oR9GvyLzKxhavh7QP19rLtLhxNo7wtBgIlmOSz2i3DujOx6t6UH1ZqJpAzWEw==", + "requested": "[9.0.1-preview.1.24570.5, )", + "resolved": "9.0.1-preview.1.24570.5", + "contentHash": "RvW4bluCCwiC72EWiLUWnkdRifsr12xd3EoyEzKMz/zNFVgB06c4XGEmah+wixWqSlxO8YXOClqefNbeNs6nwQ==", "dependencies": { - "Microsoft.Extensions.AI.Abstractions": "9.0.0-preview.9.24556.5", + "Microsoft.Extensions.AI.Abstractions": "9.0.1-preview.1.24570.5", "System.Net.Http.Json": "8.0.1", "System.Text.Json": "8.0.5" } }, "Microsoft.Extensions.AI.OpenAI": { "type": "Direct", - "requested": "[9.0.0-preview.9.24556.5, )", - "resolved": "9.0.0-preview.9.24556.5", - "contentHash": "VVNISh8h6iq4yTTqG4qaCam8DjdCThe4TE35wPLSkvRNXivAjJ+1Cu+yzRc9pB3Hm4ot0Kko6C0u8+CiXJkE0Q==", + "requested": "[9.0.1-preview.1.24570.5, )", + "resolved": "9.0.1-preview.1.24570.5", + "contentHash": "ysYX0124k5QdW2F9KGpqFjATE6SOQZx55lDOIsNXMrz+0NyAJVRuQfaV1WNTGslKgUP0QL/kfeRz/oMqqM+Urw==", "dependencies": { - "Microsoft.Extensions.AI.Abstractions": "9.0.0-preview.9.24556.5", - "OpenAI": "2.0.0", + "Microsoft.Extensions.AI.Abstractions": "9.0.1-preview.1.24570.5", + "OpenAI": "2.1.0-beta.2", "System.Memory.Data": "8.0.1", "System.Text.Json": "8.0.5" } @@ -126,14 +127,15 @@ }, "Microsoft.Extensions.Configuration.Json": { "type": "Direct", - "requested": "[8.0.1, )", - "resolved": "8.0.1", - "contentHash": "L89DLNuimOghjV3tLx0ArFDwVEJD6+uGB3BMCMX01kaLzXkaXHb2021xOMl2QOxUxbdePKUZsUY7n2UUkycjRg==", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "WiTK0LrnsqmedrbzwL7f4ZUo+/wByqy2eKab39I380i2rd8ImfCRMrtkqJVGDmfqlkP/YzhckVOwPc5MPrSNpg==", "dependencies": { - "Microsoft.Extensions.Configuration": "8.0.0", - "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", - "Microsoft.Extensions.Configuration.FileExtensions": "8.0.1", - "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0" + "Microsoft.Extensions.Configuration": "9.0.0", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.0", + "Microsoft.Extensions.Configuration.FileExtensions": "9.0.0", + "Microsoft.Extensions.FileProviders.Abstractions": "9.0.0", + "System.Text.Json": "9.0.0" } }, "Microsoft.Extensions.DependencyInjection": { @@ -147,34 +149,45 @@ }, "Microsoft.Extensions.FileSystemGlobbing": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "OK+670i7esqlQrPjdIKRbsyMCe9g5kSLpRRQGSr4Q58AOYEe/hCnfLZprh7viNisSUUQZmMrbbuDaIrP+V1ebQ==" + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "jGFKZiXs2HNseK3NK/rfwHNNovER71jSj4BD1a/649ml9+h6oEtYd0GSALZDNW8jZ2Rh+oAeadOa6sagYW1F2A==" }, "Microsoft.Extensions.Http": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "cWz4caHwvx0emoYe7NkHPxII/KkTI8R/LC9qdqJqnKv2poTJ4e2qqPGQqvRoQ5kaSA4FU5IV3qFAuLuOhoqULQ==", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "DqI4q54U4hH7bIAq9M5a/hl5Odr/KBAoaZ0dcT4OgutD8dook34CbkvAfAIzkMVjYXiL+E5ul9etwwqiX4PHGw==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Diagnostics": "8.0.0", - "Microsoft.Extensions.Logging": "8.0.0", - "Microsoft.Extensions.Logging.Abstractions": "8.0.0", - "Microsoft.Extensions.Options": "8.0.0" + "Microsoft.Extensions.Configuration.Abstractions": "9.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", + "Microsoft.Extensions.Diagnostics": "9.0.0", + "Microsoft.Extensions.Logging": "9.0.0", + "Microsoft.Extensions.Logging.Abstractions": "9.0.0", + "Microsoft.Extensions.Options": "9.0.0" } }, "Microsoft.Extensions.Http.Resilience": { "type": "Direct", - "requested": "[8.8.0, )", - "resolved": "8.8.0", - "contentHash": "A1endJCDOjxF095DrWQhchzwNBFLPiNcerh2x8sbiFbUXQfLaOTeaUfgp9iTMFsSpwhAD3qcSEMMsgFlJ0tDxg==", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "XkzXX1r+J6wxzR9jLZtJhW+h2x3Ff/hgwVBS9hrzAS7CUFaQgL+oYTAFiP6fyp93ruDxx0rDi/kGzy3KWUDCiQ==", "dependencies": { "Microsoft.Extensions.Configuration.Binder": "8.0.2", - "Microsoft.Extensions.Http.Diagnostics": "8.8.0", - "Microsoft.Extensions.ObjectPool": "8.0.8", - "Microsoft.Extensions.Resilience": "8.8.0" + "Microsoft.Extensions.Http.Diagnostics": "9.0.0", + "Microsoft.Extensions.ObjectPool": "8.0.11", + "Microsoft.Extensions.Resilience": "9.0.0" + } + }, + "Microsoft.Extensions.Logging": { + "type": "Direct", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "crjWyORoug0kK7RSNJBTeSE6VX8IQgLf3nUpTB9m62bPXp/tzbnOsnbe8TXEG0AASNaKZddnpHKw7fET8E++Pg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "9.0.0", + "Microsoft.Extensions.Logging.Abstractions": "9.0.0", + "Microsoft.Extensions.Options": "9.0.0" } }, "Microsoft.Extensions.Logging.Console": { @@ -193,13 +206,13 @@ }, "Microsoft.Extensions.Logging.Debug": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "dt0x21qBdudHLW/bjMJpkixv858RRr8eSomgVbU8qljOyfrfDGi1JQvpF9w8S7ziRPtRKisuWaOwFxJM82GxeA==", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "4wGlHsrLhYjLw4sFkfRixu2w4DK7dv60OjbvgbLGhUJk0eUPxYHhnszZ/P18nnAkfrPryvtOJ3ZTVev0kpqM6A==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Logging": "8.0.0", - "Microsoft.Extensions.Logging.Abstractions": "8.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", + "Microsoft.Extensions.Logging": "9.0.0", + "Microsoft.Extensions.Logging.Abstractions": "9.0.0" } }, "Microsoft.Extensions.Options": { @@ -233,30 +246,45 @@ }, "Sentry.Extensions.Logging": { "type": "Direct", - "requested": "[4.12.1, )", - "resolved": "4.12.1", - "contentHash": "0u8QJxh58+i9D1EKBpcH87J8iAOAYB0oLzyWKMIW1NSUHND19pf9ZMG5gE/F4/iNRFefZ7GAVMyNpepg+mmL7w==", + "requested": "[4.13.0, )", + "resolved": "4.13.0", + "contentHash": "yZ5+TtJKWcss6cG17YjnovImx4X56T8O6Qy6bsMC8tMDttYy8J7HJ2F+WdaZNyjOCo0Rfi6N2gc+Clv/5pf+TQ==", "dependencies": { "Microsoft.Extensions.Configuration.Binder": "8.0.0", "Microsoft.Extensions.Http": "8.0.0", "Microsoft.Extensions.Logging.Configuration": "8.0.0", - "Sentry": "4.12.1" + "Sentry": "4.13.0" } }, "Sentry.Profiling": { "type": "Direct", - "requested": "[4.12.1, )", - "resolved": "4.12.1", - "contentHash": "UbYfATbG7c8NH2TR5RrWeT2oySAsTB3pTcOKj24dCtIngpsp0qO/8ROwuUg8eYaIrRk3egHlf0ST1w+FV/w0jA==", + "requested": "[4.13.0, )", + "resolved": "4.13.0", + "contentHash": "FJM5SDwHGN2+ceVGCeVQLHXNSBQEHSpvPrBVqVdbbduIdf7yWvaCdCn8MZyCJsvOuKMpwbrle6lXtxrKOZ2ESA==", "dependencies": { "Microsoft.Diagnostics.NETCore.Client": "0.2.510501", - "Sentry": "4.12.1" + "Sentry": "4.13.0" + } + }, + "System.Diagnostics.DiagnosticSource": { + "type": "Direct", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "ddppcFpnbohLWdYKr/ZeLZHmmI+DXFgZ3Snq+/E7SwcdW4UnvxmaugkwGywvGVWkHPGCSZjCP+MLzu23AL5SDw==" + }, + "System.Net.Http.Json": { + "type": "Direct", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "e8s0BFOwHaFTqz6wi3l+9tm0SuKOUs5wiahHTjnAabF9n1+0NuZeG/vOLo2vSfUp+DlIChaRfnAiOFkRpHN/ew==", + "dependencies": { + "System.Text.Json": "9.0.0" } }, "ExcelDna.Integration": { "type": "Transitive", - "resolved": "1.8.0", - "contentHash": "Z7UYuY291cTxc8lEESlUT73iI28yZ9LeJnvhPIRpZHBlrjbCN8lnEWvSmUqmUn1lBNGk6p2n0GlPuBhopJuksA==" + "resolved": "1.9.0-alpha3", + "contentHash": "vQZP+bXCXkiIx4bkntd2iWnMDS57ECcul2XFQvMSvgcTy6+2q81xI2Sp6FGXr9tU/t7eHfpG7vVv/7TZKOLnxA==" }, "Humanizer.Core": { "type": "Transitive", @@ -300,11 +328,11 @@ }, "Microsoft.Extensions.AmbientMetadata.Application": { "type": "Transitive", - "resolved": "8.8.0", - "contentHash": "R/MVBReInWAHzUiuj+JCzwOryxIuLYLEFRSU9LX+R7hcFg7WySiqmAIj9aa4JSsNm6EkEpxTXCxTB4HeIT0m8A==", + "resolved": "9.0.0", + "contentHash": "F/pe4t8UXc65l0KFLoUDtYttoLI0qtXiWQ4HStw3Cq2ExaNARxS4isAvA6w0PNCyIs8dLxkaruJHeyKN8mK1lA==", "dependencies": { "Microsoft.Extensions.Configuration": "8.0.0", - "Microsoft.Extensions.Hosting.Abstractions": "8.0.0", + "Microsoft.Extensions.Hosting.Abstractions": "8.0.1", "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0" } }, @@ -318,11 +346,11 @@ }, "Microsoft.Extensions.Compliance.Abstractions": { "type": "Transitive", - "resolved": "8.8.0", - "contentHash": "v5vsjDXh/GceR9k8qglsvEJeW4YC7E9Cd7nokr8FcJ6CG5APKxDhEn4zeUsNEsFWRKOSoi96r2tUnf5nGbScFg==", + "resolved": "9.0.0", + "contentHash": "Y85iXLdg55OYwJR23KvCYAPDaIa+723vus9WzLROVWMy0sblHlxVtPR01QR+mwVFbYiljqa9NknmELVitzxbXw==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.1", - "Microsoft.Extensions.ObjectPool": "8.0.8" + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.ObjectPool": "8.0.11" } }, "Microsoft.Extensions.Configuration.Abstractions": { @@ -343,14 +371,14 @@ }, "Microsoft.Extensions.Configuration.FileExtensions": { "type": "Transitive", - "resolved": "8.0.1", - "contentHash": "EJzSNO9oaAXnTdtdNO6npPRsIIeZCBSNmdQ091VDO7fBiOtJAAeEq6dtrVXIi3ZyjC5XRSAtVvF8SzcneRHqKQ==", + "resolved": "9.0.0", + "contentHash": "4EK93Jcd2lQG4GY6PAw8jGss0ZzFP0vPc1J85mES5fKNuDTqgFXHba9onBw2s18fs3I4vdo2AWyfD1mPAxWSQQ==", "dependencies": { - "Microsoft.Extensions.Configuration": "8.0.0", - "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", - "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", - "Microsoft.Extensions.FileProviders.Physical": "8.0.0", - "Microsoft.Extensions.Primitives": "8.0.0" + "Microsoft.Extensions.Configuration": "9.0.0", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.0", + "Microsoft.Extensions.FileProviders.Abstractions": "9.0.0", + "Microsoft.Extensions.FileProviders.Physical": "9.0.0", + "Microsoft.Extensions.Primitives": "9.0.0" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { @@ -360,92 +388,82 @@ }, "Microsoft.Extensions.DependencyInjection.AutoActivation": { "type": "Transitive", - "resolved": "8.8.0", - "contentHash": "DBuKYUr3lvl1jFYPoNIIiXS9dcs3Xxp0HXhPj1xLYp1XFueCZMeiN/RDC1vWdQCK15aFAMZxuxEBBJQD7LxY+g==", + "resolved": "9.0.0", + "contentHash": "Wv4APocpxkPf0zcEFDwGrdwP6tOOSSQ5ByHUxRYcvXu8CXY0N0Pf95usTVs605jG2txxLOpRESH6ficdPDNiTg==", "dependencies": { - "Microsoft.Extensions.Hosting.Abstractions": "8.0.0" + "Microsoft.Extensions.Hosting.Abstractions": "8.0.1" } }, "Microsoft.Extensions.Diagnostics": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "3PZp/YSkIXrF7QK7PfC1bkyRYwqOHpWFad8Qx+4wkuumAeXo1NHaxpS9LboNA9OvNSAu+QOVlXbMyoY+pHSqcw==", + "resolved": "9.0.0", + "contentHash": "0CF9ZrNw5RAlRfbZuVIvzzhP8QeWqHiUmMBU/2H7Nmit8/vwP3/SbHeEctth7D4Gz2fBnEbokPc1NU8/j/1ZLw==", "dependencies": { - "Microsoft.Extensions.Configuration": "8.0.0", - "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0", - "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0" + "Microsoft.Extensions.Configuration": "9.0.0", + "Microsoft.Extensions.Diagnostics.Abstractions": "9.0.0", + "Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.0" } }, "Microsoft.Extensions.Diagnostics.Abstractions": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "JHYCQG7HmugNYUhOl368g+NMxYE/N/AiclCYRNlgCY9eVyiBkOHMwK4x60RYMxv9EL3+rmj1mqHvdCiPpC+D4Q==", + "resolved": "9.0.0", + "contentHash": "1K8P7XzuzX8W8pmXcZjcrqS6x5eSSdvhQohmcpgiQNY/HlDAlnrhR9dvlURfFz428A+RTCJpUyB+aKTA6AgVcQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Options": "8.0.0", - "System.Diagnostics.DiagnosticSource": "8.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", + "Microsoft.Extensions.Options": "9.0.0", + "System.Diagnostics.DiagnosticSource": "9.0.0" } }, "Microsoft.Extensions.Diagnostics.ExceptionSummarization": { "type": "Transitive", - "resolved": "8.8.0", - "contentHash": "K9jlnvIbkYvHRAwxl8WjFkIU7D/5PYAeVmDMAN6Y4IQyWyBUT/xMotYwYGRFAO2kiPPJhIIj7a9VI2p7ydakkg==", + "resolved": "9.0.0", + "contentHash": "eRuTbk4jB0JcNEcYngXNAiLM6tpDyuUfFd13bPy/TJCdHU3IP+C7dZN7qu+Z7mOg3CmxS78YtRULQX8QhL+fMQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.1" + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2" } }, "Microsoft.Extensions.FileProviders.Abstractions": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "ZbaMlhJlpisjuWbvXr4LdAst/1XxH3vZ6A0BsgTphZ2L4PGuxRLz7Jr/S7mkAAnOn78Vu0fKhEgNF5JO3zfjqQ==", + "resolved": "9.0.0", + "contentHash": "uK439QzYR0q2emLVtYzwyK3x+T5bTY4yWsd/k/ZUS9LR6Sflp8MIdhGXW8kQCd86dQD4tLqvcbLkku8qHY263Q==", "dependencies": { - "Microsoft.Extensions.Primitives": "8.0.0" + "Microsoft.Extensions.Primitives": "9.0.0" } }, "Microsoft.Extensions.FileProviders.Physical": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "UboiXxpPUpwulHvIAVE36Knq0VSHaAmfrFkegLyBZeaADuKezJ/AIXYAW8F5GBlGk/VaibN2k/Zn1ca8YAfVdA==", + "resolved": "9.0.0", + "contentHash": "3+ZUSpOSmie+o8NnLIRqCxSh65XL/ExU7JYnFOg58awDRlY3lVpZ9A369jkoZL1rpsq7LDhEfkn2ghhGaY1y5Q==", "dependencies": { - "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", - "Microsoft.Extensions.FileSystemGlobbing": "8.0.0", - "Microsoft.Extensions.Primitives": "8.0.0" + "Microsoft.Extensions.FileProviders.Abstractions": "9.0.0", + "Microsoft.Extensions.FileSystemGlobbing": "9.0.0", + "Microsoft.Extensions.Primitives": "9.0.0" } }, "Microsoft.Extensions.Hosting.Abstractions": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "AG7HWwVRdCHlaA++1oKDxLsXIBxmDpMPb3VoyOoAghEWnkUvEAdYQUwnV4jJbAaa/nMYNiEh5ByoLauZBEiovg==", + "resolved": "8.0.1", + "contentHash": "nHwq9aPBdBPYXPti6wYEEfgXddfBrYC+CQLn+qISiwQq5tpfaqDZSKOJNxoe9rfQxGf1c+2wC/qWFe1QYJPYqw==", "dependencies": { "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.1", "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", - "Microsoft.Extensions.Logging.Abstractions": "8.0.0" + "Microsoft.Extensions.Logging.Abstractions": "8.0.2" } }, "Microsoft.Extensions.Http.Diagnostics": { "type": "Transitive", - "resolved": "8.8.0", - "contentHash": "N2o2E+SDU3Lu7RD8U2Nw3G2WPEPGx2nBNAueqm3nQPnWWGzFlkE6eRqGp1Lwvey8aiFnPbJ0rHoiM1m+RCt/Bg==", + "resolved": "9.0.0", + "contentHash": "68vpnYcWc/6WZQItq1P4bwJ5K14jFrj0BvFl7yIKwsGrQK+hvqU/1sqT/DnjmvgXSUAnTLluAtBIVMomadYD/g==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.AutoActivation": "8.8.0", - "Microsoft.Extensions.Http": "8.0.0", + "Microsoft.Extensions.DependencyInjection.AutoActivation": "9.0.0", + "Microsoft.Extensions.Http": "8.0.1", "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0", - "Microsoft.Extensions.Telemetry": "8.8.0", + "Microsoft.Extensions.Telemetry": "9.0.0", "Microsoft.IO.RecyclableMemoryStream": "3.0.0" } }, - "Microsoft.Extensions.Logging": { - "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "crjWyORoug0kK7RSNJBTeSE6VX8IQgLf3nUpTB9m62bPXp/tzbnOsnbe8TXEG0AASNaKZddnpHKw7fET8E++Pg==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection": "9.0.0", - "Microsoft.Extensions.Logging.Abstractions": "9.0.0", - "Microsoft.Extensions.Options": "9.0.0" - } - }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", "resolved": "9.0.0", @@ -472,8 +490,8 @@ }, "Microsoft.Extensions.ObjectPool": { "type": "Transitive", - "resolved": "8.0.8", - "contentHash": "wnjTFjEvvSbOs3iMfl6CeJcUgPHZMYUB9uAQbGQGxGwVRl4GydNpMSkVntTzoi7AqQeYumU9yDSNeVbpq+ebow==" + "resolved": "8.0.11", + "contentHash": "6ApKcHNJigXBfZa6XlDQ8feJpq7SG1ogZXg6M4FiNzgd6irs3LUAzo0Pfn4F2ZI9liGnH1XIBR/OtSbZmJAV5w==" }, "Microsoft.Extensions.Primitives": { "type": "Transitive", @@ -482,37 +500,37 @@ }, "Microsoft.Extensions.Resilience": { "type": "Transitive", - "resolved": "8.8.0", - "contentHash": "Lzd/CKTv8KrudJCD7yD0tEW0D05073YbLHINdCYsWelcksdLSwu1IwjwTYru+w/PAeawu8qOU2dtnIgi+ssFSQ==", + "resolved": "9.0.0", + "contentHash": "ZwG0HaAUbtTaQ6gTnSXd+2mmh3oWbjh+/HKQJRxjBpIOmdKXVCJrs7N6nbVzAZ7GS6eilR1XJFZmjDyJ8QGhRA==", "dependencies": { - "Microsoft.Extensions.Diagnostics": "8.0.0", - "Microsoft.Extensions.Diagnostics.ExceptionSummarization": "8.8.0", + "Microsoft.Extensions.Diagnostics": "8.0.1", + "Microsoft.Extensions.Diagnostics.ExceptionSummarization": "9.0.0", "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0", - "Microsoft.Extensions.Telemetry.Abstractions": "8.8.0", - "Polly.Extensions": "8.4.1", - "Polly.RateLimiting": "8.4.1" + "Microsoft.Extensions.Telemetry.Abstractions": "9.0.0", + "Polly.Extensions": "8.4.2", + "Polly.RateLimiting": "8.4.2" } }, "Microsoft.Extensions.Telemetry": { "type": "Transitive", - "resolved": "8.8.0", - "contentHash": "3LMXqE65Sv/u160bsG7/meI55peTi68DZ5eAlOb+0FSHdg1rBwn7sNDh6arW135sOoIp/28CsZIigIX5ZPvXTA==", + "resolved": "9.0.0", + "contentHash": "aid79SF7sJmn/rpliUscwV3gnDOjqx1GmG+R/5E9mK8Cy5tNhEcGnak7RW9RSOOCzqpLEP3jOwdcH3YZ2F/kNA==", "dependencies": { - "Microsoft.Extensions.AmbientMetadata.Application": "8.8.0", - "Microsoft.Extensions.DependencyInjection.AutoActivation": "8.8.0", - "Microsoft.Extensions.Logging.Configuration": "8.0.0", - "Microsoft.Extensions.ObjectPool": "8.0.8", - "Microsoft.Extensions.Telemetry.Abstractions": "8.8.0" + "Microsoft.Extensions.AmbientMetadata.Application": "9.0.0", + "Microsoft.Extensions.DependencyInjection.AutoActivation": "9.0.0", + "Microsoft.Extensions.Logging.Configuration": "8.0.1", + "Microsoft.Extensions.ObjectPool": "8.0.11", + "Microsoft.Extensions.Telemetry.Abstractions": "9.0.0" } }, "Microsoft.Extensions.Telemetry.Abstractions": { "type": "Transitive", - "resolved": "8.8.0", - "contentHash": "6d4p22MCcWbHJtSN2vtLU35T/qDaYKmR2ZLJwtDv5FzOCF65QZN1bMsMd1KCGZte4QLn+OCs/8q0Sz9LpMCG4g==", + "resolved": "9.0.0", + "contentHash": "F84i7q3FleH1kjmaSuslZ33yzWtzxZXWjd1ylXcuwM5jNjWN2ifJSO0xQ5c5BYWZA5VeIWWUWupIYr6+3TnDqA==", "dependencies": { - "Microsoft.Extensions.Compliance.Abstractions": "8.8.0", - "Microsoft.Extensions.Logging.Abstractions": "8.0.1", - "Microsoft.Extensions.ObjectPool": "8.0.8", + "Microsoft.Extensions.Compliance.Abstractions": "9.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.2", + "Microsoft.Extensions.ObjectPool": "8.0.11", "Microsoft.Extensions.Options": "8.0.2" } }, @@ -523,56 +541,51 @@ }, "OpenAI": { "type": "Transitive", - "resolved": "2.0.0", - "contentHash": "WI9e5tC15ZBt02scCp2gcHrjxQRVX2Ws3eUu/YsICqellH3MwdRggGujSZg69oBJMtk1AYmqb9l08ix0l5AcqQ==", + "resolved": "2.1.0-beta.2", + "contentHash": "l+fZAS9XnCxnxGodYFPziMNF9u0ZBfOGEyOuryXnjkcPe+Z4g/ErEvGYyq559V+Q9C8J87eQ0lfq5KtwSk6ppw==", "dependencies": { - "System.ClientModel": "1.1.0", + "System.ClientModel": "1.2.1", "System.Diagnostics.DiagnosticSource": "6.0.1" } }, "Polly.Core": { "type": "Transitive", - "resolved": "8.4.1", - "contentHash": "bg4kE7mFwXc6FJ8NLknTgVgLAMlbToWC7vpdqAITv8lPzKpp9v7aWJPc04GRoZQaJhVY/tdr8K2/VW2aTmaA1Q==" + "resolved": "8.4.2", + "contentHash": "BpE2I6HBYYA5tF0Vn4eoQOGYTYIK1BlF5EXVgkWGn3mqUUjbXAr13J6fZVbp7Q3epRR8yshacBMlsHMhpOiV3g==" }, "Polly.Extensions": { "type": "Transitive", - "resolved": "8.4.1", - "contentHash": "NaRu+mopzJLoDm3qhklrUENIwkhmJbtzLRXK+oMb0c4bGwT84co+BM+TIwqApUfZrcz+BvA/vpB1vk6hB4XtAA==", + "resolved": "8.4.2", + "contentHash": "GZ9vRVmR0jV2JtZavt+pGUsQ1O1cuRKG7R7VOZI6ZDy9y6RNPvRvXK1tuS4ffUrv8L0FTea59oEuQzgS0R7zSA==", "dependencies": { "Microsoft.Extensions.Logging.Abstractions": "8.0.0", "Microsoft.Extensions.Options": "8.0.0", - "Polly.Core": "8.4.1" + "Polly.Core": "8.4.2" } }, "Polly.RateLimiting": { "type": "Transitive", - "resolved": "8.4.1", - "contentHash": "YF9/pUUd3VZchjJ7+KWAINv5xtHlaWUvrhpGGC73He/zz0mRHzV7gKVDzqwAZrdDk09CdunA+Gt/a37Bl/rMwQ==", + "resolved": "8.4.2", + "contentHash": "ehTImQ/eUyO07VYW2WvwSmU9rRH200SKJ/3jku9rOkyWE0A2JxNFmAVms8dSn49QLSjmjFRRSgfNyOgr/2PSmA==", "dependencies": { - "Polly.Core": "8.4.1", + "Polly.Core": "8.4.2", "System.Threading.RateLimiting": "8.0.0" } }, "Sentry": { "type": "Transitive", - "resolved": "4.12.1", - "contentHash": "OLf7885OKHWLaTLTyw884mwOT4XKCWj2Hz5Wuz/TJemJqXwCIdIljkJBIoeHviRUPvtB7ulDgeYXf/Z7ScToSA==" + "resolved": "4.13.0", + "contentHash": "Wfw3M1WpFcrYaGzPm7QyUTfIOYkVXQ1ry6p4WYjhbLz9fPwV23SGQZTFDpdox67NHM0V0g1aoQ4YKLm4ANtEEg==" }, "System.ClientModel": { "type": "Transitive", - "resolved": "1.1.0", - "contentHash": "UocOlCkxLZrG2CKMAAImPcldJTxeesHnHGHwhJ0pNlZEvEXcWKuQvVOER2/NiOkJGRJk978SNdw3j6/7O9H1lg==", + "resolved": "1.2.1", + "contentHash": "s9+M5El+DXdCRRLzxak8uGBKWT8H/eIssGpFtpaMKdJULrQbBDPH/zFbVyHX+NDczhS5EvjHFbBH9/L+0UhmcA==", "dependencies": { - "System.Memory.Data": "1.0.2", - "System.Text.Json": "6.0.9" + "System.Memory.Data": "6.0.0", + "System.Text.Json": "6.0.10" } }, - "System.Diagnostics.DiagnosticSource": { - "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "ddppcFpnbohLWdYKr/ZeLZHmmI+DXFgZ3Snq+/E7SwcdW4UnvxmaugkwGywvGVWkHPGCSZjCP+MLzu23AL5SDw==" - }, "System.IO.Pipelines": { "type": "Transitive", "resolved": "9.0.0", @@ -583,11 +596,6 @@ "resolved": "8.0.1", "contentHash": "BVYuec3jV23EMRDeR7Dr1/qhx7369dZzJ9IWy2xylvb4YfXsrUxspWc4UWYid/tj4zZK58uGZqn2WQiaDMhmAg==" }, - "System.Net.Http.Json": { - "type": "Transitive", - "resolved": "8.0.1", - "contentHash": "F423ZLoJFYg1s6iA+Y7BLflVKjEK5XEA2+Z9CHbxJEUtS3+R5pgnFN499QzriRjYpOu6kS2Crd2YBkOFDHrblg==" - }, "System.Text.Encodings.Web": { "type": "Transitive", "resolved": "9.0.0", @@ -602,6 +610,11 @@ "System.Text.Encodings.Web": "9.0.0" } }, + "System.Threading.Channels": { + "type": "Transitive", + "resolved": "9.0.0", + "contentHash": "hzACdIf1C+4Dqos5ijV404b94+LqfIC8nfS3mNpCDFWowb1N3PNfJPopneq32ahWlDeyaPZJqjBk76YFR69Rpg==" + }, "System.Threading.RateLimiting": { "type": "Transitive", "resolved": "8.0.0",