Skip to content

Commit 1ff27fe

Browse files
authored
Added Hugging Face inference provider (#397)
1 parent 712ed29 commit 1ff27fe

13 files changed

+281
-37
lines changed

app/MindWork AI Studio.sln.DotSettings

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@
33
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=EDI/@EntryIndexedValue">EDI</s:String>
44
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ERI/@EntryIndexedValue">ERI</s:String>
55
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GWDG/@EntryIndexedValue">GWDG</s:String>
6+
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HF/@EntryIndexedValue">HF</s:String>
67
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LLM/@EntryIndexedValue">LLM</s:String>
78
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LM/@EntryIndexedValue">LM</s:String>
89
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MSG/@EntryIndexedValue">MSG</s:String>
910
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RAG/@EntryIndexedValue">RAG</s:String>
1011
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UI/@EntryIndexedValue">UI</s:String>
12+
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=URL/@EntryIndexedValue">URL</s:String>
1113
<s:Boolean x:Key="/Default/UserDictionary/Words/=agentic/@EntryIndexedValue">True</s:Boolean>
1214
<s:Boolean x:Key="/Default/UserDictionary/Words/=groq/@EntryIndexedValue">True</s:Boolean>
1315
<s:Boolean x:Key="/Default/UserDictionary/Words/=gwdg/@EntryIndexedValue">True</s:Boolean>
16+
<s:Boolean x:Key="/Default/UserDictionary/Words/=huggingface/@EntryIndexedValue">True</s:Boolean>
1417
<s:Boolean x:Key="/Default/UserDictionary/Words/=mwais/@EntryIndexedValue">True</s:Boolean>
1518
<s:Boolean x:Key="/Default/UserDictionary/Words/=ollama/@EntryIndexedValue">True</s:Boolean>
1619
<s:Boolean x:Key="/Default/UserDictionary/Words/=tauri_0027s/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
<RowTemplate>
3030
<MudTd>@context.Num</MudTd>
3131
<MudTd>@context.InstanceName</MudTd>
32-
<MudTd>@context.UsedLLMProvider</MudTd>
32+
<MudTd>@context.UsedLLMProvider.ToName()</MudTd>
3333
<MudTd>
3434
@if (context.UsedLLMProvider is not LLMProviders.SELF_HOSTED)
3535
{

app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ private async Task EditLLMProvider(AIStudio.Settings.Provider provider)
6565
{ x => x.IsSelfHosted, provider.IsSelfHosted },
6666
{ x => x.IsEditing, true },
6767
{ x => x.DataHost, provider.Host },
68+
{ x => x.HfInstanceProviderId, provider.HFInstanceProvider },
6869
};
6970

7071
var dialogReference = await this.DialogService.ShowAsync<ProviderDialog>("Edit LLM Provider", dialogParameters, DialogOptions.FULLSCREEN);

app/MindWork AI Studio/Dialogs/ProviderDialog.razor

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@using AIStudio.Provider
2+
@using AIStudio.Provider.HuggingFace
23
@using AIStudio.Provider.SelfHosted
34

45
<MudDialog>
@@ -28,38 +29,55 @@
2829
InputType="InputType.Password"
2930
Validation="@this.providerValidation.ValidatingAPIKey"
3031
/>
31-
32-
<MudTextField
33-
T="string"
34-
@bind-Text="@this.DataHostname"
35-
Label="Hostname"
36-
Disabled="@(!this.DataLLMProvider.IsHostnameNeeded())"
37-
Class="mb-3"
38-
Adornment="Adornment.Start"
39-
AdornmentIcon="@Icons.Material.Filled.Dns"
40-
AdornmentColor="Color.Info"
41-
Validation="@this.providerValidation.ValidatingHostname"
42-
UserAttributes="@SPELLCHECK_ATTRIBUTES"
43-
/>
4432

45-
<MudSelect Disabled="@(!this.DataLLMProvider.IsHostNeeded())" @bind-Value="@this.DataHost" Label="Host" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingHost">
46-
@foreach (Host host in Enum.GetValues(typeof(Host)))
47-
{
48-
<MudSelectItem Value="@host">@host.Name()</MudSelectItem>
49-
}
50-
</MudSelect>
33+
@if (this.DataLLMProvider.IsHostnameNeeded())
34+
{
35+
<MudTextField
36+
T="string"
37+
@bind-Text="@this.DataHostname"
38+
Label="Hostname"
39+
Disabled="@(!this.DataLLMProvider.IsHostnameNeeded())"
40+
Class="mb-3"
41+
Adornment="Adornment.Start"
42+
AdornmentIcon="@Icons.Material.Filled.Dns"
43+
AdornmentColor="Color.Info"
44+
Validation="@this.providerValidation.ValidatingHostname"
45+
UserAttributes="@SPELLCHECK_ATTRIBUTES"/>
46+
}
47+
48+
@if (this.DataLLMProvider.IsHostNeeded())
49+
{
50+
<MudSelect Disabled="@(!this.DataLLMProvider.IsHostNeeded())" @bind-Value="@this.DataHost" Label="Host" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingHost">
51+
@foreach (Host host in Enum.GetValues(typeof(Host)))
52+
{
53+
<MudSelectItem Value="@host">@host.Name()</MudSelectItem>
54+
}
55+
</MudSelect>
56+
}
57+
58+
@if (this.DataLLMProvider.IsHFInstanceProviderNeeded())
59+
{
60+
<MudSelect Disabled="@(!this.DataLLMProvider.IsHFInstanceProviderNeeded())" @bind-Value="@this.HfInstanceProviderId" Label="HF Instance Provider" Class="mb-3" OpenIcon="@Icons.Material.Filled.Dns" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingHFInstanceProvider">
61+
@foreach (HFInstanceProvider instanceProvider in Enum.GetValues(typeof(HFInstanceProvider)))
62+
{
63+
<MudSelectItem Value="@instanceProvider">@instanceProvider.ToName()</MudSelectItem>
64+
}
65+
</MudSelect>
66+
67+
<MudJustifiedText Class="mb-3"> Please double-check if your model name matches the curl specifications provided by the instance provider. If it doesn't, you might get a <b>Not Found</b> error when trying to use the model. Here's a <MudLink Href="https://huggingface.co/meta-llama/Llama-3.1-8B-Instruct?inference_api=true&inference_provider=novita&language=sh" Target="_blank">curl example</MudLink>.</MudJustifiedText>
68+
}
5169

5270
<MudStack Row="@true" AlignItems="AlignItems.Center">
5371
@if (this.DataLLMProvider.IsLLMModelProvidedManually())
5472
{
55-
<MudButton Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.DataLLMProvider.GetModelsOverviewURL()" Target="_blank">Show available models</MudButton>
73+
<MudButton Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.DataLLMProvider.GetModelsOverviewURL(this.HfInstanceProviderId)" Target="_blank">Show available models</MudButton>
5674
<MudTextField
5775
T="string"
5876
@bind-Text="@this.dataManuallyModel"
5977
Label="Model"
6078
Class="mb-3"
6179
Adornment="Adornment.Start"
62-
AdornmentIcon="@Icons.Material.Filled.Dns"
80+
AdornmentIcon="@Icons.Material.Filled.FaceRetouchingNatural"
6381
AdornmentColor="Color.Info"
6482
Validation="@this.ValidateManuallyModel"
6583
UserAttributes="@SPELLCHECK_ATTRIBUTES"

app/MindWork AI Studio/Dialogs/ProviderDialog.razor.cs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using AIStudio.Provider;
2+
using AIStudio.Provider.HuggingFace;
23
using AIStudio.Settings;
34
using AIStudio.Tools.Services;
45
using AIStudio.Tools.Validation;
@@ -47,6 +48,12 @@ public partial class ProviderDialog : ComponentBase, ISecretId
4748
[Parameter]
4849
public Host DataHost { get; set; } = Host.NONE;
4950

51+
/// <summary>
52+
/// The HFInstanceProvider to use, e.g., CEREBRAS.
53+
/// </summary>
54+
[Parameter]
55+
public HFInstanceProvider HfInstanceProviderId { get; set; } = HFInstanceProvider.NONE;
56+
5057
/// <summary>
5158
/// Is this provider self-hosted?
5259
/// </summary>
@@ -122,10 +129,16 @@ private AIStudio.Settings.Provider CreateProviderSettings()
122129
Id = this.DataId,
123130
InstanceName = this.DataInstanceName,
124131
UsedLLMProvider = this.DataLLMProvider,
125-
Model = this.DataLLMProvider is LLMProviders.FIREWORKS ? new Model(this.dataManuallyModel, null) : this.DataModel,
132+
Model = this.DataLLMProvider switch
133+
{
134+
LLMProviders.FIREWORKS => new Model(this.dataManuallyModel, null),
135+
LLMProviders.HUGGINGFACE => new Model(this.dataManuallyModel, null),
136+
_ => this.DataModel
137+
},
126138
IsSelfHosted = this.DataLLMProvider is LLMProviders.SELF_HOSTED,
127139
Hostname = cleanedHostname.EndsWith('/') ? cleanedHostname[..^1] : cleanedHostname,
128140
Host = this.DataHost,
141+
HFInstanceProvider = this.HfInstanceProviderId,
129142
};
130143
}
131144

@@ -146,8 +159,8 @@ protected override async Task OnInitializedAsync()
146159
{
147160
this.dataEditingPreviousInstanceName = this.DataInstanceName.ToLowerInvariant();
148161

149-
// When using Fireworks, we must copy the model name:
150-
if (this.DataLLMProvider is LLMProviders.FIREWORKS)
162+
// When using Fireworks or Hugging Face, we must copy the model name:
163+
if (this.DataLLMProvider is LLMProviders.FIREWORKS or LLMProviders.HUGGINGFACE)
151164
this.dataManuallyModel = this.DataModel.Id;
152165

153166
//
@@ -230,7 +243,7 @@ private async Task Store()
230243

231244
private string? ValidateManuallyModel(string manuallyModel)
232245
{
233-
if (this.DataLLMProvider is LLMProviders.FIREWORKS && string.IsNullOrWhiteSpace(manuallyModel))
246+
if ((this.DataLLMProvider is LLMProviders.FIREWORKS or LLMProviders.HUGGINGFACE) && string.IsNullOrWhiteSpace(manuallyModel))
234247
return "Please enter a model name.";
235248

236249
return null;

app/MindWork AI Studio/Provider/Confidence.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ No provider selected. Please select a provider to get see its confidence level.
3535
""",
3636
};
3737

38-
public static readonly Confidence USA_NOT_TRUSTED = new()
38+
public static readonly Confidence USA_HUB = new()
3939
{
40-
Level = ConfidenceLevel.UNTRUSTED,
41-
Description = "The provider operates its service from the USA and is subject to **U.S. jurisdiction**. In case of suspicion, authorities in the USA can access your data. The provider's terms of service state that **all your data can be used by the provider at will.**",
40+
Level = ConfidenceLevel.UNKNOWN,
41+
Description = "The provider operates its service from the USA and is subject to **U.S. jurisdiction**. In case of suspicion, authorities in the USA can access your data. Please inform yourself about the use of your data. We do not know if your data is safe.",
4242
};
4343

4444
public static readonly Confidence UNKNOWN = new()
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace AIStudio.Provider.HuggingFace;
2+
3+
/// <summary>
4+
/// Enum for instance providers that Hugging Face supports.
5+
/// </summary>
6+
public enum HFInstanceProvider
7+
{
8+
NONE,
9+
10+
CEREBRAS,
11+
NEBIUS_AI_STUDIO,
12+
SAMBANOVA,
13+
NOVITA,
14+
HYPERBOLIC,
15+
TOGETHER_AI,
16+
FIREWORKS,
17+
HF_INFERENCE_API,
18+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
namespace AIStudio.Provider.HuggingFace;
2+
3+
public static class HFInstanceProviderExtensions
4+
{
5+
public static string Endpoints(this HFInstanceProvider provider, Model model) => provider switch
6+
{
7+
HFInstanceProvider.CEREBRAS => "cerebras/v1/",
8+
HFInstanceProvider.NEBIUS_AI_STUDIO => "nebius/v1/",
9+
HFInstanceProvider.SAMBANOVA => "sambanova/v1/",
10+
HFInstanceProvider.NOVITA => "novita/v3/openai/",
11+
HFInstanceProvider.HYPERBOLIC => "hyperbolic/v1/",
12+
HFInstanceProvider.TOGETHER_AI => "together/v1/",
13+
HFInstanceProvider.FIREWORKS => "fireworks-ai/inference/v1/",
14+
HFInstanceProvider.HF_INFERENCE_API => $"hf-inference/models/{model.ToString()}/v1/",
15+
_ => string.Empty,
16+
};
17+
18+
public static string EndpointsId(this HFInstanceProvider provider) => provider switch
19+
{
20+
HFInstanceProvider.CEREBRAS => "cerebras",
21+
HFInstanceProvider.NEBIUS_AI_STUDIO => "nebius",
22+
HFInstanceProvider.SAMBANOVA => "sambanova",
23+
HFInstanceProvider.NOVITA => "novita",
24+
HFInstanceProvider.HYPERBOLIC => "hyperbolic",
25+
HFInstanceProvider.TOGETHER_AI => "together",
26+
HFInstanceProvider.FIREWORKS => "fireworks",
27+
HFInstanceProvider.HF_INFERENCE_API => "hf-inference",
28+
_ => string.Empty,
29+
};
30+
31+
public static string ToName(this HFInstanceProvider provider) => provider switch
32+
{
33+
HFInstanceProvider.CEREBRAS => "Cerebras",
34+
HFInstanceProvider.NEBIUS_AI_STUDIO => "Nebius AI Studio",
35+
HFInstanceProvider.SAMBANOVA => "Sambanova",
36+
HFInstanceProvider.NOVITA => "Novita",
37+
HFInstanceProvider.HYPERBOLIC => "Hyperbolic",
38+
HFInstanceProvider.TOGETHER_AI => "Together AI",
39+
HFInstanceProvider.FIREWORKS => "Fireworks AI",
40+
HFInstanceProvider.HF_INFERENCE_API => "Hugging Face Inference API",
41+
_ => string.Empty,
42+
};
43+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
using System.Net.Http.Headers;
2+
using System.Runtime.CompilerServices;
3+
using System.Text;
4+
using System.Text.Json;
5+
6+
using AIStudio.Chat;
7+
using AIStudio.Provider.OpenAI;
8+
using AIStudio.Settings;
9+
10+
namespace AIStudio.Provider.HuggingFace;
11+
12+
public sealed class ProviderHuggingFace : BaseProvider
13+
{
14+
public ProviderHuggingFace(ILogger logger, HFInstanceProvider hfProvider, Model model) : base($"https://router.huggingface.co/{hfProvider.Endpoints(model)}", logger)
15+
{
16+
logger.LogInformation($"We use the instance provider '{hfProvider}'. Thus we use the base URL 'https://router.huggingface.co/{hfProvider.Endpoints(model)}'.");
17+
}
18+
19+
#region Implementation of IProvider
20+
21+
/// <inheritdoc />
22+
public override string Id => LLMProviders.HUGGINGFACE.ToName();
23+
24+
/// <inheritdoc />
25+
public override string InstanceName { get; set; } = "HuggingFace";
26+
27+
/// <inheritdoc />
28+
public override async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
29+
{
30+
// Get the API key:
31+
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
32+
if(!requestedSecret.Success)
33+
yield break;
34+
35+
// Prepare the system prompt:
36+
var systemPrompt = new Message
37+
{
38+
Role = "system",
39+
Content = chatThread.PrepareSystemPrompt(settingsManager, chatThread, this.logger),
40+
};
41+
42+
// Prepare the HuggingFace HTTP chat request:
43+
var huggingfaceChatRequest = JsonSerializer.Serialize(new ChatRequest
44+
{
45+
Model = chatModel.Id,
46+
47+
// Build the messages:
48+
// - First of all the system prompt
49+
// - Then none-empty user and AI messages
50+
Messages = [systemPrompt, ..chatThread.Blocks.Where(n => n.ContentType is ContentType.TEXT && !string.IsNullOrWhiteSpace((n.Content as ContentText)?.Text)).Select(n => new Message
51+
{
52+
Role = n.Role switch
53+
{
54+
ChatRole.USER => "user",
55+
ChatRole.AI => "assistant",
56+
ChatRole.AGENT => "assistant",
57+
ChatRole.SYSTEM => "system",
58+
59+
_ => "user",
60+
},
61+
62+
Content = n.Content switch
63+
{
64+
ContentText text => text.Text,
65+
_ => string.Empty,
66+
}
67+
}).ToList()],
68+
Stream = true,
69+
}, JSON_SERIALIZER_OPTIONS);
70+
71+
async Task<HttpRequestMessage> RequestBuilder()
72+
{
73+
// Build the HTTP post request:
74+
var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions");
75+
76+
// Set the authorization header:
77+
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION));
78+
79+
// Set the content:
80+
request.Content = new StringContent(huggingfaceChatRequest, Encoding.UTF8, "application/json");
81+
return request;
82+
}
83+
84+
await foreach (var content in this.StreamChatCompletionInternal<ResponseStreamLine>("HuggingFace", RequestBuilder, token))
85+
yield return content;
86+
}
87+
88+
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
89+
/// <inheritdoc />
90+
public override async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
91+
{
92+
yield break;
93+
}
94+
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
95+
96+
/// <inheritdoc />
97+
public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
98+
{
99+
return Task.FromResult(Enumerable.Empty<Model>());
100+
}
101+
102+
/// <inheritdoc />
103+
public override Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
104+
{
105+
return Task.FromResult(Enumerable.Empty<Model>());
106+
}
107+
108+
/// <inheritdoc />
109+
public override Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default)
110+
{
111+
return Task.FromResult(Enumerable.Empty<Model>());
112+
}
113+
114+
#endregion
115+
}

app/MindWork AI Studio/Provider/LLMProviders.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public enum LLMProviders
1717

1818
FIREWORKS = 5,
1919
GROQ = 6,
20+
HUGGINGFACE = 13,
2021

2122
SELF_HOSTED = 4,
2223

0 commit comments

Comments
 (0)