From ae2b9e9f41d99c11922d4458af16f1da5f2eac11 Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Sat, 20 Dec 2025 22:02:57 +0100 Subject: [PATCH] Added models listing endpoint --- src/Server/AIProvider.cs | 73 ++++++++++++++++++++++ src/Server/Controllers/ServerController.cs | 38 +++++++++++ src/Shared/Models/ServerModels.cs | 15 +++++ 3 files changed, 126 insertions(+) create mode 100644 src/Server/Controllers/ServerController.cs create mode 100644 src/Shared/Models/ServerModels.cs diff --git a/src/Server/AIProvider.cs b/src/Server/AIProvider.cs index 0a244a2..1e5c485 100644 --- a/src/Server/AIProvider.cs +++ b/src/Server/AIProvider.cs @@ -116,6 +116,79 @@ public class AIProvider throw; } } + + public string[] GetModels() + { + var aIProviders = aIProvidersConfiguration.AiProviders; + List results = []; + foreach (KeyValuePair aIProviderKV in aIProviders) + { + string aIProviderName = aIProviderKV.Key; + AIProviderConfiguration aIProvider = aIProviderKV.Value; + + using var httpClient = new HttpClient(); + + string modelNameJsonPath = ""; + Uri baseUri = new(aIProvider.BaseURL); + Uri requestUri; + string[][] requestHeaders = []; + switch (aIProvider.Handler) + { + case "ollama": + modelNameJsonPath = "$.models[*].name"; + requestUri = new Uri(baseUri, "/api/tags"); + break; + case "openai": + modelNameJsonPath = "$.data[*].id"; + requestUri = new Uri(baseUri, "/v1/models"); + if (aIProvider.ApiKey is not null) + { + requestHeaders = [ + ["Authorization", $"Bearer {aIProvider.ApiKey}"] + ]; + } + break; + default: + _logger.LogError("Unknown handler {aIProvider.Handler} in AiProvider {provider}.", [aIProvider.Handler, aIProvider]); + throw new ServerConfigurationException($"Unknown handler {aIProvider.Handler} in AiProvider {aIProvider}."); + } + + var request = new HttpRequestMessage() + { + RequestUri = requestUri, + Method = HttpMethod.Post + }; + + foreach (var header in requestHeaders) + { + request.Headers.Add(header[0], header[1]); + } + HttpResponseMessage response = httpClient.GetAsync(requestUri).Result; + string responseContent = response.Content.ReadAsStringAsync().Result; + try + { + JObject responseContentJson = JObject.Parse(responseContent); + IEnumerable? responseContentTokens = responseContentJson.SelectTokens(modelNameJsonPath); + if (responseContentTokens is null) + { + _logger.LogError("Unable to select tokens using JSONPath {modelNameJsonPath} for string: {responseContent}.", [modelNameJsonPath, responseContent]); + throw new JSONPathSelectionException(modelNameJsonPath, responseContent); + } + IEnumerable aIProviderResult = responseContentTokens.Values(); + foreach (string? result in aIProviderResult) + { + if (result is null) continue; + results.Add(aIProviderName + ":" + result); + } + } + catch (Exception ex) + { + _logger.LogError("Unable to parse the response to valid models. {ex.Message}", [ex.Message]); + throw; + } + } + return [.. results]; + } } public class AIProvidersConfiguration diff --git a/src/Server/Controllers/ServerController.cs b/src/Server/Controllers/ServerController.cs new file mode 100644 index 0000000..ca4d9b8 --- /dev/null +++ b/src/Server/Controllers/ServerController.cs @@ -0,0 +1,38 @@ +namespace Server.Controllers; + +using System.Text.Json; +using ElmahCore; +using Microsoft.AspNetCore.Mvc; +using Server.Exceptions; +using Server.Helper; +using Shared.Models; + +[ApiController] +[Route("[controller]")] +public class ServerController : ControllerBase +{ + private readonly ILogger _logger; + private readonly IConfiguration _config; + private AIProvider _aIProvider; + + public ServerController(ILogger logger, IConfiguration config, AIProvider aIProvider) + { + _logger = logger; + _config = config; + _aIProvider = aIProvider; + } + + [HttpGet("GetModels")] + public ActionResult GetModels() + { + try + { + string[] models = _aIProvider.GetModels(); + return new ServerGetModelsResult() { Models = models, Success = true }; + } catch (Exception ex) + { + _logger.LogError("Unable to get models due to exception {ex.Message} - {ex.StackTrace}", [ex.Message, ex.StackTrace]); + return new ServerGetModelsResult() { Success = false, Message = ex.Message}; + } + } +} diff --git a/src/Shared/Models/ServerModels.cs b/src/Shared/Models/ServerModels.cs new file mode 100644 index 0000000..278582e --- /dev/null +++ b/src/Shared/Models/ServerModels.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace Shared.Models; + +public class ServerGetModelsResult +{ + [JsonPropertyName("Success")] + public required bool Success { get; set; } + + [JsonPropertyName("Message")] + public string? Message { get; set; } + + [JsonPropertyName("Models")] + public string[]? Models { get; set; } +} \ No newline at end of file