Allow selection of self-hosted models

This commit is contained in:
Thorsten Sommer 2024-07-16 10:14:22 +02:00
parent 8af8426f5d
commit 52caf96cf4
No known key found for this signature in database
GPG Key ID: B0B7E2FC074BF1F5
4 changed files with 89 additions and 29 deletions

View File

@ -1,5 +1,6 @@
@page "/settings" @page "/settings"
@using AIStudio.Provider @using AIStudio.Provider
@using Host = AIStudio.Provider.SelfHosted.Host
<MudText Typo="Typo.h3" Class="mb-12">Settings</MudText> <MudText Typo="Typo.h3" Class="mb-12">Settings</MudText>
@ -27,9 +28,17 @@
<MudTd>@context.UsedProvider</MudTd> <MudTd>@context.UsedProvider</MudTd>
<MudTd> <MudTd>
@if (context.UsedProvider is not Providers.SELF_HOSTED) @if (context.UsedProvider is not Providers.SELF_HOSTED)
{
@context.Model @context.Model
}
else if (context.UsedProvider is Providers.SELF_HOSTED && context.Host is not Host.LLAMACPP)
{
@context.Model
}
else else
{
@("as selected by provider") @("as selected by provider")
}
</MudTd> </MudTd>
<MudTd Style="text-align: left;"> <MudTd Style="text-align: left;">
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.OpenInBrowser" Class="ma-2" Href="@this.GetProviderDashboardURL(context.UsedProvider)" Target="_blank" Disabled="@(context.UsedProvider is Providers.NONE or Providers.SELF_HOSTED)"> <MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.OpenInBrowser" Class="ma-2" Href="@this.GetProviderDashboardURL(context.UsedProvider)" Target="_blank" Disabled="@(context.UsedProvider is Providers.NONE or Providers.SELF_HOSTED)">

View File

@ -33,7 +33,7 @@ public sealed class ProviderSelfHosted(Settings.Provider provider) : BaseProvide
// Prepare the OpenAI HTTP chat request: // Prepare the OpenAI HTTP chat request:
var providerChatRequest = JsonSerializer.Serialize(new ChatRequest var providerChatRequest = JsonSerializer.Serialize(new ChatRequest
{ {
Model = (await this.GetTextModels(jsRuntime, settings, token: token)).First().Id, Model = chatModel.Id,
// Build the messages: // Build the messages:
// - First of all the system prompt // - First of all the system prompt
@ -137,17 +137,33 @@ public sealed class ProviderSelfHosted(Settings.Provider provider) : BaseProvide
public async Task<IEnumerable<Provider.Model>> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default) public async Task<IEnumerable<Provider.Model>> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default)
{ {
var request = new HttpRequestMessage(HttpMethod.Get, "models"); try
var response = await this.httpClient.SendAsync(request, token); {
if(!response.IsSuccessStatusCode) switch (provider.Host)
{
case Host.LLAMACPP:
// Right now, llama.cpp only supports one model.
// There is no API to list the model(s).
return [ new Provider.Model("as configured by llama.cpp") ];
case Host.LM_STUDIO:
case Host.OLLAMA:
var lmStudioRequest = new HttpRequestMessage(HttpMethod.Get, "models");
var lmStudioResponse = await this.httpClient.SendAsync(lmStudioRequest, token);
if(!lmStudioResponse.IsSuccessStatusCode)
return []; return [];
var modelResponse = await response.Content.ReadFromJsonAsync<ModelsResponse>(token); var lmStudioModelResponse = await lmStudioResponse.Content.ReadFromJsonAsync<ModelsResponse>(token);
if (modelResponse.Data.Length > 1) return lmStudioModelResponse.Data.Select(n => new Provider.Model(n.Id));
Console.WriteLine("Warning: multiple models found; using the first one."); }
var firstModel = modelResponse.Data.First(); return [];
return [ new Provider.Model(firstModel.Id) ]; }
catch(Exception e)
{
Console.WriteLine($"Failed to load text models from self-hosted provider: {e.Message}");
return [];
}
} }
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously

View File

@ -51,8 +51,8 @@
</MudSelect> </MudSelect>
<MudStack Row="@true" AlignItems="AlignItems.Center"> <MudStack Row="@true" AlignItems="AlignItems.Center">
<MudButton Disabled="@(!this.CanLoadModels)" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="this.ReloadModels">Load</MudButton> <MudButton Disabled="@(!this.CanLoadModels())" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="this.ReloadModels">Load</MudButton>
<MudSelect Disabled="@this.IsSelfHostedOrNone" @bind-Value="@this.DataModel" Label="Model" Class="mb-3" OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.ValidatingModel"> <MudSelect Disabled="@this.IsNoneProvider" @bind-Value="@this.DataModel" Label="Model" Class="mb-3" OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.ValidatingModel">
@foreach (var model in this.availableModels) @foreach (var model in this.availableModels)
{ {
<MudSelectItem Value="@model">@model</MudSelectItem> <MudSelectItem Value="@model">@model</MudSelectItem>

View File

@ -122,10 +122,11 @@ public partial class ProviderDialog : ComponentBase
this.dataEditingPreviousInstanceName = this.DataInstanceName.ToLowerInvariant(); this.dataEditingPreviousInstanceName = this.DataInstanceName.ToLowerInvariant();
// //
// We cannot load the API key nor models for self-hosted providers: // We cannot load the API key for self-hosted providers:
// //
if (this.DataProvider is Providers.SELF_HOSTED) if (this.DataProvider is Providers.SELF_HOSTED)
{ {
await this.ReloadModels();
await base.OnInitializedAsync(); await base.OnInitializedAsync();
return; return;
} }
@ -182,7 +183,8 @@ public partial class ProviderDialog : ComponentBase
// Use the data model to store the provider. // Use the data model to store the provider.
// We just return this data to the parent component: // We just return this data to the parent component:
var addedProviderSettings = this.CreateProviderSettings(); var addedProviderSettings = this.CreateProviderSettings();
if (addedProviderSettings.UsedProvider != Providers.SELF_HOSTED)
{
// We need to instantiate the provider to store the API key: // We need to instantiate the provider to store the API key:
var provider = addedProviderSettings.CreateProvider(); var provider = addedProviderSettings.CreateProvider();
@ -194,6 +196,7 @@ public partial class ProviderDialog : ComponentBase
await this.form.Validate(); await this.form.Validate();
return; return;
} }
}
this.MudDialog.Close(DialogResult.Ok(addedProviderSettings)); this.MudDialog.Close(DialogResult.Ok(addedProviderSettings));
} }
@ -219,7 +222,7 @@ public partial class ProviderDialog : ComponentBase
private string? ValidatingModel(Model model) private string? ValidatingModel(Model model)
{ {
if(this.DataProvider is Providers.SELF_HOSTED) if(this.DataProvider is Providers.SELF_HOSTED && this.DataHost == Host.LLAMACPP)
return null; return null;
if (model == default) if (model == default)
@ -319,12 +322,44 @@ public partial class ProviderDialog : ComponentBase
this.availableModels.AddRange(orderedModels); this.availableModels.AddRange(orderedModels);
} }
private bool CanLoadModels => !string.IsNullOrWhiteSpace(this.dataAPIKey) && this.DataProvider != Providers.NONE && this.DataProvider != Providers.SELF_HOSTED; private bool CanLoadModels()
{
if (this.DataProvider is Providers.SELF_HOSTED)
{
switch (this.DataHost)
{
case Host.NONE:
return false;
case Host.LLAMACPP:
return false;
case Host.LM_STUDIO:
return true;
case Host.OLLAMA:
return true;
default:
return false;
}
}
if(this.DataProvider is Providers.NONE)
return false;
if(string.IsNullOrWhiteSpace(this.dataAPIKey))
return false;
return true;
}
private bool IsCloudProvider => this.DataProvider is not Providers.SELF_HOSTED; private bool IsCloudProvider => this.DataProvider is not Providers.SELF_HOSTED;
private bool IsSelfHostedOrNone => this.DataProvider is Providers.SELF_HOSTED or Providers.NONE; private bool IsSelfHostedOrNone => this.DataProvider is Providers.SELF_HOSTED or Providers.NONE;
private bool IsNoneProvider => this.DataProvider is Providers.NONE;
private string GetProviderCreationURL() => this.DataProvider switch private string GetProviderCreationURL() => this.DataProvider switch
{ {
Providers.OPEN_AI => "https://platform.openai.com/signup", Providers.OPEN_AI => "https://platform.openai.com/signup",