From 42f4f46523529e97bee0951717070475acc7f555 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Peer=20Sch=C3=BCtt?=
<20603780+peerschuett@users.noreply.github.com>
Date: Wed, 8 Apr 2026 14:52:25 +0200
Subject: [PATCH] First version of the refactoring of ChatRequests
---
.../AlibabaCloud/ProviderAlibabaCloud.cs | 68 +++++----------
.../Provider/BaseProvider.cs | 72 ++++++++++++++++
.../Provider/DeepSeek/ProviderDeepSeek.cs | 68 +++++----------
.../Provider/Fireworks/ChatRequest.cs | 20 -----
.../Provider/Fireworks/ProviderFireworks.cs | 71 +++++-----------
.../Provider/GWDG/ProviderGWDG.cs | 68 +++++----------
.../Provider/Google/ChatRequest.cs | 20 -----
.../Provider/Google/ProviderGoogle.cs | 68 +++++----------
.../Provider/Groq/ChatRequest.cs | 5 +-
.../Provider/Groq/ProviderGroq.cs | 71 ++++++----------
.../Provider/Helmholtz/ProviderHelmholtz.cs | 68 +++++----------
.../HuggingFace/ProviderHuggingFace.cs | 71 +++++-----------
.../Provider/Mistral/ProviderMistral.cs | 76 ++++++-----------
.../Provider/OpenRouter/ProviderOpenRouter.cs | 78 +++++++----------
.../Provider/Perplexity/ProviderPerplexity.cs | 69 +++++----------
.../Provider/SelfHosted/ChatRequest.cs | 20 -----
.../Provider/SelfHosted/ProviderSelfHosted.cs | 83 +++++++------------
.../Provider/X/ProviderX.cs | 70 +++++-----------
18 files changed, 387 insertions(+), 679 deletions(-)
delete mode 100644 app/MindWork AI Studio/Provider/Fireworks/ChatRequest.cs
delete mode 100644 app/MindWork AI Studio/Provider/Google/ChatRequest.cs
delete mode 100644 app/MindWork AI Studio/Provider/SelfHosted/ChatRequest.cs
diff --git a/app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs b/app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs
index 3535809d..7f2bf792 100644
--- a/app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs
+++ b/app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs
@@ -1,7 +1,5 @@
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
-using System.Text;
-using System.Text.Json;
using AIStudio.Chat;
using AIStudio.Provider.OpenAI;
@@ -24,52 +22,30 @@ public sealed class ProviderAlibabaCloud() : BaseProvider(LLMProviders.ALIBABA_C
///
public override async IAsyncEnumerable StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{
- // Get the API key:
- var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
- if(!requestedSecret.Success)
- yield break;
-
- // Prepare the system prompt:
- var systemPrompt = new TextMessage
- {
- Role = "system",
- Content = chatThread.PrepareSystemPrompt(settingsManager),
- };
-
- // Parse the API parameters:
- var apiParameters = this.ParseAdditionalApiParameters();
-
- // Build the list of messages:
- var messages = await chatThread.Blocks.BuildMessagesUsingNestedImageUrlAsync(this.Provider, chatModel);
-
- // Prepare the AlibabaCloud HTTP chat request:
- var alibabaCloudChatRequest = JsonSerializer.Serialize(new ChatCompletionAPIRequest
- {
- Model = chatModel.Id,
-
- // Build the messages:
- // - First of all the system prompt
- // - Then none-empty user and AI messages
- Messages = [systemPrompt, ..messages],
-
- Stream = true,
- AdditionalApiParameters = apiParameters
- }, JSON_SERIALIZER_OPTIONS);
+ await foreach (var content in this.StreamOpenAICompatibleChatCompletion(
+ "AlibabaCloud",
+ chatModel,
+ chatThread,
+ settingsManager,
+ async (systemPrompt, apiParameters) =>
+ {
+ // Build the list of messages:
+ var messages = await chatThread.Blocks.BuildMessagesUsingNestedImageUrlAsync(this.Provider, chatModel);
- async Task RequestBuilder()
- {
- // Build the HTTP post request:
- var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions");
+ return new ChatCompletionAPIRequest
+ {
+ Model = chatModel.Id,
- // Set the authorization header:
- request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION));
+ // Build the messages:
+ // - First of all the system prompt
+ // - Then none-empty user and AI messages
+ Messages = [systemPrompt, ..messages],
- // Set the content:
- request.Content = new StringContent(alibabaCloudChatRequest, Encoding.UTF8, "application/json");
- return request;
- }
-
- await foreach (var content in this.StreamChatCompletionInternal("AlibabaCloud", RequestBuilder, token))
+ Stream = true,
+ AdditionalApiParameters = apiParameters
+ };
+ },
+ token: token))
yield return content;
}
@@ -183,4 +159,4 @@ public sealed class ProviderAlibabaCloud() : BaseProvider(LLMProviders.ALIBABA_C
return modelResponse.Data.Where(model => prefixes.Any(prefix => model.Id.StartsWith(prefix, StringComparison.InvariantCulture)));
}
-}
\ No newline at end of file
+}
diff --git a/app/MindWork AI Studio/Provider/BaseProvider.cs b/app/MindWork AI Studio/Provider/BaseProvider.cs
index 9b729824..46e43843 100644
--- a/app/MindWork AI Studio/Provider/BaseProvider.cs
+++ b/app/MindWork AI Studio/Provider/BaseProvider.cs
@@ -565,6 +565,78 @@ public abstract class BaseProvider : IProvider, ISecretId
streamReader.Dispose();
}
+ ///
+ /// Streams the chat completion from an OpenAI-compatible provider using the Chat Completion API.
+ ///
+ /// The provider name for logging and error reporting.
+ /// The selected chat model.
+ /// The current chat thread.
+ /// The settings manager.
+ /// Builds the provider-specific request body.
+ /// The secret store type.
+ /// Whether the API key is optional.
+ /// The system prompt role to use.
+ /// The request path, relative to the provider base URL.
+ /// Optional additional headers to add.
+ /// The cancellation token.
+ /// The request DTO type.
+ /// The delta stream line type.
+ /// The annotation stream line type.
+ /// The streamed content chunks.
+ protected async IAsyncEnumerable StreamOpenAICompatibleChatCompletion(
+ string providerName,
+ Model chatModel,
+ ChatThread chatThread,
+ SettingsManager settingsManager,
+ Func, Task> requestFactory,
+ SecretStoreType storeType = SecretStoreType.LLM_PROVIDER,
+ bool isTryingSecret = false,
+ string systemPromptRole = "system",
+ string requestPath = "chat/completions",
+ Action? headersAction = null,
+ [EnumeratorCancellation] CancellationToken token = default)
+ where TDelta : IResponseStreamLine
+ where TAnnotation : IAnnotationStreamLine
+ {
+ // Get the API key:
+ var requestedSecret = await RUST_SERVICE.GetAPIKey(this, storeType, isTrying: isTryingSecret);
+ if(!requestedSecret.Success && !isTryingSecret)
+ yield break;
+
+ // Prepare the system prompt:
+ var systemPrompt = new TextMessage
+ {
+ Role = systemPromptRole,
+ Content = chatThread.PrepareSystemPrompt(settingsManager),
+ };
+
+ // Parse the API parameters:
+ var apiParameters = this.ParseAdditionalApiParameters();
+
+ // Prepare the provider HTTP chat request:
+ var providerChatRequest = JsonSerializer.Serialize(await requestFactory(systemPrompt, apiParameters), JSON_SERIALIZER_OPTIONS);
+
+ async Task RequestBuilder()
+ {
+ // Build the HTTP post request:
+ var request = new HttpRequestMessage(HttpMethod.Post, requestPath);
+
+ // Set the authorization header:
+ if (requestedSecret.Success)
+ request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION));
+
+ // Set provider-specific headers:
+ headersAction?.Invoke(request.Headers);
+
+ // Set the content:
+ request.Content = new StringContent(providerChatRequest, Encoding.UTF8, "application/json");
+ return request;
+ }
+
+ await foreach (var content in this.StreamChatCompletionInternal(providerName, RequestBuilder, token))
+ yield return content;
+ }
+
protected async Task PerformStandardTranscriptionRequest(RequestedSecret requestedSecret, Model transcriptionModel, string audioFilePath, Host host = Host.NONE, CancellationToken token = default)
{
try
diff --git a/app/MindWork AI Studio/Provider/DeepSeek/ProviderDeepSeek.cs b/app/MindWork AI Studio/Provider/DeepSeek/ProviderDeepSeek.cs
index e1ae306a..bc1e0806 100644
--- a/app/MindWork AI Studio/Provider/DeepSeek/ProviderDeepSeek.cs
+++ b/app/MindWork AI Studio/Provider/DeepSeek/ProviderDeepSeek.cs
@@ -1,7 +1,5 @@
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
-using System.Text;
-using System.Text.Json;
using AIStudio.Chat;
using AIStudio.Provider.OpenAI;
@@ -24,52 +22,30 @@ public sealed class ProviderDeepSeek() : BaseProvider(LLMProviders.DEEP_SEEK, "h
///
public override async IAsyncEnumerable StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{
- // Get the API key:
- var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
- if(!requestedSecret.Success)
- yield break;
-
- // Prepare the system prompt:
- var systemPrompt = new TextMessage
- {
- Role = "system",
- Content = chatThread.PrepareSystemPrompt(settingsManager),
- };
-
- // Parse the API parameters:
- var apiParameters = this.ParseAdditionalApiParameters();
-
- // Build the list of messages:
- var messages = await chatThread.Blocks.BuildMessagesUsingDirectImageUrlAsync(this.Provider, chatModel);
-
- // Prepare the DeepSeek HTTP chat request:
- var deepSeekChatRequest = JsonSerializer.Serialize(new ChatCompletionAPIRequest
- {
- Model = chatModel.Id,
-
- // Build the messages:
- // - First of all the system prompt
- // - Then none-empty user and AI messages
- Messages = [systemPrompt, ..messages],
-
- Stream = true,
- AdditionalApiParameters = apiParameters
- }, JSON_SERIALIZER_OPTIONS);
+ await foreach (var content in this.StreamOpenAICompatibleChatCompletion(
+ "DeepSeek",
+ chatModel,
+ chatThread,
+ settingsManager,
+ async (systemPrompt, apiParameters) =>
+ {
+ // Build the list of messages:
+ var messages = await chatThread.Blocks.BuildMessagesUsingDirectImageUrlAsync(this.Provider, chatModel);
- async Task RequestBuilder()
- {
- // Build the HTTP post request:
- var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions");
+ return new ChatCompletionAPIRequest
+ {
+ Model = chatModel.Id,
- // Set the authorization header:
- request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION));
+ // Build the messages:
+ // - First of all the system prompt
+ // - Then none-empty user and AI messages
+ Messages = [systemPrompt, ..messages],
- // Set the content:
- request.Content = new StringContent(deepSeekChatRequest, Encoding.UTF8, "application/json");
- return request;
- }
-
- await foreach (var content in this.StreamChatCompletionInternal("DeepSeek", RequestBuilder, token))
+ Stream = true,
+ AdditionalApiParameters = apiParameters
+ };
+ },
+ token: token))
yield return content;
}
@@ -144,4 +120,4 @@ public sealed class ProviderDeepSeek() : BaseProvider(LLMProviders.DEEP_SEEK, "h
var modelResponse = await response.Content.ReadFromJsonAsync(token);
return modelResponse.Data;
}
-}
\ No newline at end of file
+}
diff --git a/app/MindWork AI Studio/Provider/Fireworks/ChatRequest.cs b/app/MindWork AI Studio/Provider/Fireworks/ChatRequest.cs
deleted file mode 100644
index 54963feb..00000000
--- a/app/MindWork AI Studio/Provider/Fireworks/ChatRequest.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System.Text.Json.Serialization;
-
-namespace AIStudio.Provider.Fireworks;
-
-///
-/// The Fireworks chat request model.
-///
-/// Which model to use for chat completion.
-/// The chat messages.
-/// Whether to stream the chat completion.
-public readonly record struct ChatRequest(
- string Model,
- IList Messages,
- bool Stream
-)
-{
- // Attention: The "required" modifier is not supported for [JsonExtensionData].
- [JsonExtensionData]
- public IDictionary AdditionalApiParameters { get; init; } = new Dictionary();
-}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Provider/Fireworks/ProviderFireworks.cs b/app/MindWork AI Studio/Provider/Fireworks/ProviderFireworks.cs
index 2254b7ad..0091e7a1 100644
--- a/app/MindWork AI Studio/Provider/Fireworks/ProviderFireworks.cs
+++ b/app/MindWork AI Studio/Provider/Fireworks/ProviderFireworks.cs
@@ -1,7 +1,4 @@
-using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
-using System.Text;
-using System.Text.Json;
using AIStudio.Chat;
using AIStudio.Provider.OpenAI;
@@ -24,53 +21,31 @@ public class ProviderFireworks() : BaseProvider(LLMProviders.FIREWORKS, "https:/
///
public override async IAsyncEnumerable StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{
- // Get the API key:
- var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
- if(!requestedSecret.Success)
- yield break;
+ await foreach (var content in this.StreamOpenAICompatibleChatCompletion(
+ "Fireworks",
+ chatModel,
+ chatThread,
+ settingsManager,
+ async (systemPrompt, apiParameters) =>
+ {
+ // Build the list of messages:
+ var messages = await chatThread.Blocks.BuildMessagesUsingNestedImageUrlAsync(this.Provider, chatModel);
- // Prepare the system prompt:
- var systemPrompt = new TextMessage
- {
- Role = "system",
- Content = chatThread.PrepareSystemPrompt(settingsManager),
- };
-
- // Parse the API parameters:
- var apiParameters = this.ParseAdditionalApiParameters();
-
- // Build the list of messages:
- var messages = await chatThread.Blocks.BuildMessagesUsingNestedImageUrlAsync(this.Provider, chatModel);
-
- // Prepare the Fireworks HTTP chat request:
- var fireworksChatRequest = JsonSerializer.Serialize(new ChatRequest
- {
- Model = chatModel.Id,
-
- // Build the messages:
- // - First of all the system prompt
- // - Then none-empty user and AI messages
- Messages = [systemPrompt, ..messages],
-
- // Right now, we only support streaming completions:
- Stream = true,
- AdditionalApiParameters = apiParameters
- }, JSON_SERIALIZER_OPTIONS);
+ return new ChatCompletionAPIRequest
+ {
+ Model = chatModel.Id,
- async Task RequestBuilder()
- {
- // Build the HTTP post request:
- var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions");
+ // Build the messages:
+ // - First of all the system prompt
+ // - Then none-empty user and AI messages
+ Messages = [systemPrompt, ..messages],
- // Set the authorization header:
- request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION));
-
- // Set the content:
- request.Content = new StringContent(fireworksChatRequest, Encoding.UTF8, "application/json");
- return request;
- }
-
- await foreach (var content in this.StreamChatCompletionInternal("Fireworks", RequestBuilder, token))
+ // Right now, we only support streaming completions:
+ Stream = true,
+ AdditionalApiParameters = apiParameters
+ };
+ },
+ token: token))
yield return content;
}
@@ -126,4 +101,4 @@ public class ProviderFireworks() : BaseProvider(LLMProviders.FIREWORKS, "https:/
}
#endregion
-}
\ No newline at end of file
+}
diff --git a/app/MindWork AI Studio/Provider/GWDG/ProviderGWDG.cs b/app/MindWork AI Studio/Provider/GWDG/ProviderGWDG.cs
index 41e19fa9..edae7ae9 100644
--- a/app/MindWork AI Studio/Provider/GWDG/ProviderGWDG.cs
+++ b/app/MindWork AI Studio/Provider/GWDG/ProviderGWDG.cs
@@ -1,7 +1,5 @@
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
-using System.Text;
-using System.Text.Json;
using AIStudio.Chat;
using AIStudio.Provider.OpenAI;
@@ -24,52 +22,30 @@ public sealed class ProviderGWDG() : BaseProvider(LLMProviders.GWDG, "https://ch
///
public override async IAsyncEnumerable StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{
- // Get the API key:
- var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
- if(!requestedSecret.Success)
- yield break;
-
- // Prepare the system prompt:
- var systemPrompt = new TextMessage
- {
- Role = "system",
- Content = chatThread.PrepareSystemPrompt(settingsManager),
- };
-
- // Parse the API parameters:
- var apiParameters = this.ParseAdditionalApiParameters();
-
- // Build the list of messages:
- var messages = await chatThread.Blocks.BuildMessagesUsingNestedImageUrlAsync(this.Provider, chatModel);
-
- // Prepare the GWDG HTTP chat request:
- var gwdgChatRequest = JsonSerializer.Serialize(new ChatCompletionAPIRequest
- {
- Model = chatModel.Id,
-
- // Build the messages:
- // - First of all the system prompt
- // - Then none-empty user and AI messages
- Messages = [systemPrompt, ..messages],
-
- Stream = true,
- AdditionalApiParameters = apiParameters
- }, JSON_SERIALIZER_OPTIONS);
+ await foreach (var content in this.StreamOpenAICompatibleChatCompletion(
+ "GWDG",
+ chatModel,
+ chatThread,
+ settingsManager,
+ async (systemPrompt, apiParameters) =>
+ {
+ // Build the list of messages:
+ var messages = await chatThread.Blocks.BuildMessagesUsingNestedImageUrlAsync(this.Provider, chatModel);
- async Task RequestBuilder()
- {
- // Build the HTTP post request:
- var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions");
+ return new ChatCompletionAPIRequest
+ {
+ Model = chatModel.Id,
- // Set the authorization header:
- request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION));
+ // Build the messages:
+ // - First of all the system prompt
+ // - Then none-empty user and AI messages
+ Messages = [systemPrompt, ..messages],
- // Set the content:
- request.Content = new StringContent(gwdgChatRequest, Encoding.UTF8, "application/json");
- return request;
- }
-
- await foreach (var content in this.StreamChatCompletionInternal("GWDG", RequestBuilder, token))
+ Stream = true,
+ AdditionalApiParameters = apiParameters
+ };
+ },
+ token: token))
yield return content;
}
@@ -152,4 +128,4 @@ public sealed class ProviderGWDG() : BaseProvider(LLMProviders.GWDG, "https://ch
var modelResponse = await response.Content.ReadFromJsonAsync(token);
return modelResponse.Data;
}
-}
\ No newline at end of file
+}
diff --git a/app/MindWork AI Studio/Provider/Google/ChatRequest.cs b/app/MindWork AI Studio/Provider/Google/ChatRequest.cs
deleted file mode 100644
index 1a898c3a..00000000
--- a/app/MindWork AI Studio/Provider/Google/ChatRequest.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System.Text.Json.Serialization;
-
-namespace AIStudio.Provider.Google;
-
-///
-/// The Google chat request model.
-///
-/// Which model to use for chat completion.
-/// The chat messages.
-/// Whether to stream the chat completion.
-public readonly record struct ChatRequest(
- string Model,
- IList Messages,
- bool Stream
-)
-{
- // Attention: The "required" modifier is not supported for [JsonExtensionData].
- [JsonExtensionData]
- public IDictionary AdditionalApiParameters { get; init; } = new Dictionary();
-}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs b/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs
index 8a86fcbe..0caf7b05 100644
--- a/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs
+++ b/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs
@@ -24,53 +24,31 @@ public class ProviderGoogle() : BaseProvider(LLMProviders.GOOGLE, "https://gener
///
public override async IAsyncEnumerable StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{
- // Get the API key:
- var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
- if(!requestedSecret.Success)
- yield break;
+ await foreach (var content in this.StreamOpenAICompatibleChatCompletion(
+ "Google",
+ chatModel,
+ chatThread,
+ settingsManager,
+ async (systemPrompt, apiParameters) =>
+ {
+ // Build the list of messages:
+ var messages = await chatThread.Blocks.BuildMessagesUsingNestedImageUrlAsync(this.Provider, chatModel);
- // Prepare the system prompt:
- var systemPrompt = new TextMessage
- {
- Role = "system",
- Content = chatThread.PrepareSystemPrompt(settingsManager),
- };
-
- // Parse the API parameters:
- var apiParameters = this.ParseAdditionalApiParameters();
-
- // Build the list of messages:
- var messages = await chatThread.Blocks.BuildMessagesUsingNestedImageUrlAsync(this.Provider, chatModel);
-
- // Prepare the Google HTTP chat request:
- var geminiChatRequest = JsonSerializer.Serialize(new ChatRequest
- {
- Model = chatModel.Id,
-
- // Build the messages:
- // - First of all the system prompt
- // - Then none-empty user and AI messages
- Messages = [systemPrompt, ..messages],
-
- // Right now, we only support streaming completions:
- Stream = true,
- AdditionalApiParameters = apiParameters
- }, JSON_SERIALIZER_OPTIONS);
+ return new ChatCompletionAPIRequest
+ {
+ Model = chatModel.Id,
- async Task RequestBuilder()
- {
- // Build the HTTP post request:
- var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions");
+ // Build the messages:
+ // - First of all the system prompt
+ // - Then none-empty user and AI messages
+ Messages = [systemPrompt, ..messages],
- // Set the authorization header:
- request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION));
-
- // Set the content:
- request.Content = new StringContent(geminiChatRequest, Encoding.UTF8, "application/json");
- return request;
- }
-
- await foreach (var content in this.StreamChatCompletionInternal("Google", RequestBuilder, token))
+ // Right now, we only support streaming completions:
+ Stream = true,
+ AdditionalApiParameters = apiParameters
+ };
+ },
+ token: token))
yield return content;
}
@@ -256,4 +234,4 @@ public class ProviderGoogle() : BaseProvider(LLMProviders.GOOGLE, "https://gener
? modelId["models/".Length..]
: modelId;
}
-}
\ No newline at end of file
+}
diff --git a/app/MindWork AI Studio/Provider/Groq/ChatRequest.cs b/app/MindWork AI Studio/Provider/Groq/ChatRequest.cs
index 2e7668f1..07ddce22 100644
--- a/app/MindWork AI Studio/Provider/Groq/ChatRequest.cs
+++ b/app/MindWork AI Studio/Provider/Groq/ChatRequest.cs
@@ -13,10 +13,11 @@ public readonly record struct ChatRequest(
string Model,
IList Messages,
bool Stream,
- int Seed
+ [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ int? Seed
)
{
// Attention: The "required" modifier is not supported for [JsonExtensionData].
[JsonExtensionData]
public IDictionary AdditionalApiParameters { get; init; } = new Dictionary();
-}
\ No newline at end of file
+}
diff --git a/app/MindWork AI Studio/Provider/Groq/ProviderGroq.cs b/app/MindWork AI Studio/Provider/Groq/ProviderGroq.cs
index 8f938667..a3ca862d 100644
--- a/app/MindWork AI Studio/Provider/Groq/ProviderGroq.cs
+++ b/app/MindWork AI Studio/Provider/Groq/ProviderGroq.cs
@@ -1,7 +1,5 @@
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
-using System.Text;
-using System.Text.Json;
using AIStudio.Chat;
using AIStudio.Provider.OpenAI;
@@ -24,53 +22,34 @@ public class ProviderGroq() : BaseProvider(LLMProviders.GROQ, "https://api.groq.
///
public override async IAsyncEnumerable StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{
- // Get the API key:
- var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
- if(!requestedSecret.Success)
- yield break;
+ await foreach (var content in this.StreamOpenAICompatibleChatCompletion(
+ "Groq",
+ chatModel,
+ chatThread,
+ settingsManager,
+ async (systemPrompt, apiParameters) =>
+ {
+ var seed = TryPopIntParameter(apiParameters, "seed", out var parsedSeed) ? parsedSeed : (int?)null;
- // Prepare the system prompt:
- var systemPrompt = new TextMessage
- {
- Role = "system",
- Content = chatThread.PrepareSystemPrompt(settingsManager),
- };
-
- // Parse the API parameters:
- var apiParameters = this.ParseAdditionalApiParameters();
-
- // Build the list of messages:
- var messages = await chatThread.Blocks.BuildMessagesUsingNestedImageUrlAsync(this.Provider, chatModel);
-
- // Prepare the OpenAI HTTP chat request:
- var groqChatRequest = JsonSerializer.Serialize(new ChatRequest
- {
- Model = chatModel.Id,
-
- // Build the messages:
- // - First of all the system prompt
- // - Then none-empty user and AI messages
- Messages = [systemPrompt, ..messages],
-
- // Right now, we only support streaming completions:
- Stream = true,
- AdditionalApiParameters = apiParameters
- }, JSON_SERIALIZER_OPTIONS);
+ // Build the list of messages:
+ var messages = await chatThread.Blocks.BuildMessagesUsingNestedImageUrlAsync(this.Provider, chatModel);
- async Task RequestBuilder()
- {
- // Build the HTTP post request:
- var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions");
+ return new ChatRequest
+ {
+ Model = chatModel.Id,
- // Set the authorization header:
- request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION));
+ // Build the messages:
+ // - First of all the system prompt
+ // - Then none-empty user and AI messages
+ Messages = [systemPrompt, ..messages],
- // Set the content:
- request.Content = new StringContent(groqChatRequest, Encoding.UTF8, "application/json");
- return request;
- }
-
- await foreach (var content in this.StreamChatCompletionInternal("Groq", RequestBuilder, token))
+ // Right now, we only support streaming completions:
+ Stream = true,
+ Seed = seed,
+ AdditionalApiParameters = apiParameters
+ };
+ },
+ token: token))
yield return content;
}
@@ -148,4 +127,4 @@ public class ProviderGroq() : BaseProvider(LLMProviders.GROQ, "https://api.groq.
!n.Id.StartsWith("distil-", StringComparison.OrdinalIgnoreCase) &&
!n.Id.Contains("-tts", StringComparison.OrdinalIgnoreCase));
}
-}
\ No newline at end of file
+}
diff --git a/app/MindWork AI Studio/Provider/Helmholtz/ProviderHelmholtz.cs b/app/MindWork AI Studio/Provider/Helmholtz/ProviderHelmholtz.cs
index 070597a3..bfa7a758 100644
--- a/app/MindWork AI Studio/Provider/Helmholtz/ProviderHelmholtz.cs
+++ b/app/MindWork AI Studio/Provider/Helmholtz/ProviderHelmholtz.cs
@@ -1,7 +1,5 @@
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
-using System.Text;
-using System.Text.Json;
using AIStudio.Chat;
using AIStudio.Provider.OpenAI;
@@ -24,52 +22,30 @@ public sealed class ProviderHelmholtz() : BaseProvider(LLMProviders.HELMHOLTZ, "
///
public override async IAsyncEnumerable StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{
- // Get the API key:
- var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
- if(!requestedSecret.Success)
- yield break;
-
- // Prepare the system prompt:
- var systemPrompt = new TextMessage
- {
- Role = "system",
- Content = chatThread.PrepareSystemPrompt(settingsManager),
- };
-
- // Parse the API parameters:
- var apiParameters = this.ParseAdditionalApiParameters();
-
- // Build the list of messages:
- var messages = await chatThread.Blocks.BuildMessagesUsingNestedImageUrlAsync(this.Provider, chatModel);
-
- // Prepare the Helmholtz HTTP chat request:
- var helmholtzChatRequest = JsonSerializer.Serialize(new ChatCompletionAPIRequest
- {
- Model = chatModel.Id,
-
- // Build the messages:
- // - First of all the system prompt
- // - Then none-empty user and AI messages
- Messages = [systemPrompt, ..messages],
-
- Stream = true,
- AdditionalApiParameters = apiParameters
- }, JSON_SERIALIZER_OPTIONS);
+ await foreach (var content in this.StreamOpenAICompatibleChatCompletion(
+ "Helmholtz",
+ chatModel,
+ chatThread,
+ settingsManager,
+ async (systemPrompt, apiParameters) =>
+ {
+ // Build the list of messages:
+ var messages = await chatThread.Blocks.BuildMessagesUsingNestedImageUrlAsync(this.Provider, chatModel);
- async Task RequestBuilder()
- {
- // Build the HTTP post request:
- var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions");
+ return new ChatCompletionAPIRequest
+ {
+ Model = chatModel.Id,
- // Set the authorization header:
- request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION));
+ // Build the messages:
+ // - First of all the system prompt
+ // - Then none-empty user and AI messages
+ Messages = [systemPrompt, ..messages],
- // Set the content:
- request.Content = new StringContent(helmholtzChatRequest, Encoding.UTF8, "application/json");
- return request;
- }
-
- await foreach (var content in this.StreamChatCompletionInternal("Helmholtz", RequestBuilder, token))
+ Stream = true,
+ AdditionalApiParameters = apiParameters
+ };
+ },
+ token: token))
yield return content;
}
@@ -151,4 +127,4 @@ public sealed class ProviderHelmholtz() : BaseProvider(LLMProviders.HELMHOLTZ, "
var modelResponse = await response.Content.ReadFromJsonAsync(token);
return modelResponse.Data;
}
-}
\ No newline at end of file
+}
diff --git a/app/MindWork AI Studio/Provider/HuggingFace/ProviderHuggingFace.cs b/app/MindWork AI Studio/Provider/HuggingFace/ProviderHuggingFace.cs
index f2e8c380..c22b5c50 100644
--- a/app/MindWork AI Studio/Provider/HuggingFace/ProviderHuggingFace.cs
+++ b/app/MindWork AI Studio/Provider/HuggingFace/ProviderHuggingFace.cs
@@ -1,7 +1,4 @@
-using System.Net.Http.Headers;
-using System.Runtime.CompilerServices;
-using System.Text;
-using System.Text.Json;
+using System.Runtime.CompilerServices;
using AIStudio.Chat;
using AIStudio.Provider.OpenAI;
@@ -29,52 +26,30 @@ public sealed class ProviderHuggingFace : BaseProvider
///
public override async IAsyncEnumerable StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{
- // Get the API key:
- var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
- if(!requestedSecret.Success)
- yield break;
+ await foreach (var content in this.StreamOpenAICompatibleChatCompletion(
+ "HuggingFace",
+ chatModel,
+ chatThread,
+ settingsManager,
+ async (systemPrompt, apiParameters) =>
+ {
+ // Build the list of messages:
+ var messages = await chatThread.Blocks.BuildMessagesUsingNestedImageUrlAsync(this.Provider, chatModel);
- // Prepare the system prompt:
- var systemPrompt = new TextMessage
- {
- Role = "system",
- Content = chatThread.PrepareSystemPrompt(settingsManager),
- };
-
- // Parse the API parameters:
- var apiParameters = this.ParseAdditionalApiParameters();
-
- // Build the list of messages:
- var message = await chatThread.Blocks.BuildMessagesUsingNestedImageUrlAsync(this.Provider, chatModel);
-
- // Prepare the HuggingFace HTTP chat request:
- var huggingfaceChatRequest = JsonSerializer.Serialize(new ChatCompletionAPIRequest
- {
- Model = chatModel.Id,
-
- // Build the messages:
- // - First of all the system prompt
- // - Then none-empty user and AI messages
- Messages = [systemPrompt, ..message],
-
- Stream = true,
- AdditionalApiParameters = apiParameters
- }, JSON_SERIALIZER_OPTIONS);
+ return new ChatCompletionAPIRequest
+ {
+ Model = chatModel.Id,
- async Task RequestBuilder()
- {
- // Build the HTTP post request:
- var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions");
+ // Build the messages:
+ // - First of all the system prompt
+ // - Then none-empty user and AI messages
+ Messages = [systemPrompt, ..messages],
- // Set the authorization header:
- request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION));
-
- // Set the content:
- request.Content = new StringContent(huggingfaceChatRequest, Encoding.UTF8, "application/json");
- return request;
- }
-
- await foreach (var content in this.StreamChatCompletionInternal("HuggingFace", RequestBuilder, token))
+ Stream = true,
+ AdditionalApiParameters = apiParameters
+ };
+ },
+ token: token))
yield return content;
}
@@ -123,4 +98,4 @@ public sealed class ProviderHuggingFace : BaseProvider
}
#endregion
-}
\ No newline at end of file
+}
diff --git a/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs b/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs
index 485729fb..b40f6657 100644
--- a/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs
+++ b/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs
@@ -1,7 +1,5 @@
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
-using System.Text;
-using System.Text.Json;
using AIStudio.Chat;
using AIStudio.Provider.OpenAI;
@@ -22,58 +20,36 @@ public sealed class ProviderMistral() : BaseProvider(LLMProviders.MISTRAL, "http
///
public override async IAsyncEnumerable StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{
- // Get the API key:
- var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
- if(!requestedSecret.Success)
- yield break;
+ await foreach (var content in this.StreamOpenAICompatibleChatCompletion(
+ "Mistral",
+ chatModel,
+ chatThread,
+ settingsManager,
+ async (systemPrompt, apiParameters) =>
+ {
+ var safePrompt = TryPopBoolParameter(apiParameters, "safe_prompt", out var parsedSafePrompt) && parsedSafePrompt;
+ var randomSeed = TryPopIntParameter(apiParameters, "random_seed", out var parsedRandomSeed) ? parsedRandomSeed : (int?)null;
- // Prepare the system prompt:
- var systemPrompt = new TextMessage
- {
- Role = "system",
- Content = chatThread.PrepareSystemPrompt(settingsManager),
- };
-
- // Parse the API parameters:
- var apiParameters = this.ParseAdditionalApiParameters();
- var safePrompt = TryPopBoolParameter(apiParameters, "safe_prompt", out var parsedSafePrompt) && parsedSafePrompt;
- var randomSeed = TryPopIntParameter(apiParameters, "random_seed", out var parsedRandomSeed) ? parsedRandomSeed : (int?)null;
+ // Build the list of messages:
+ var messages = await chatThread.Blocks.BuildMessagesUsingDirectImageUrlAsync(this.Provider, chatModel);
- // Build the list of messages:
- var messages = await chatThread.Blocks.BuildMessagesUsingDirectImageUrlAsync(this.Provider, chatModel);
-
- // Prepare the Mistral HTTP chat request:
- var mistralChatRequest = JsonSerializer.Serialize(new ChatRequest
- {
- Model = chatModel.Id,
-
- // Build the messages:
- // - First of all the system prompt
- // - Then none-empty user and AI messages
- Messages = [systemPrompt, ..messages],
-
- // Right now, we only support streaming completions:
- Stream = true,
- RandomSeed = randomSeed,
- SafePrompt = safePrompt,
- AdditionalApiParameters = apiParameters
- }, JSON_SERIALIZER_OPTIONS);
+ return new ChatRequest
+ {
+ Model = chatModel.Id,
-
- async Task RequestBuilder()
- {
- // Build the HTTP post request:
- var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions");
+ // Build the messages:
+ // - First of all the system prompt
+ // - Then none-empty user and AI messages
+ Messages = [systemPrompt, ..messages],
- // Set the authorization header:
- request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION));
-
- // Set the content:
- request.Content = new StringContent(mistralChatRequest, Encoding.UTF8, "application/json");
- return request;
- }
-
- await foreach (var content in this.StreamChatCompletionInternal("Mistral", RequestBuilder, token))
+ // Right now, we only support streaming completions:
+ Stream = true,
+ RandomSeed = randomSeed,
+ SafePrompt = safePrompt,
+ AdditionalApiParameters = apiParameters
+ };
+ },
+ token: token))
yield return content;
}
diff --git a/app/MindWork AI Studio/Provider/OpenRouter/ProviderOpenRouter.cs b/app/MindWork AI Studio/Provider/OpenRouter/ProviderOpenRouter.cs
index 4995cca9..9f2c1b13 100644
--- a/app/MindWork AI Studio/Provider/OpenRouter/ProviderOpenRouter.cs
+++ b/app/MindWork AI Studio/Provider/OpenRouter/ProviderOpenRouter.cs
@@ -1,7 +1,5 @@
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
-using System.Text;
-using System.Text.Json;
using AIStudio.Chat;
using AIStudio.Provider.OpenAI;
@@ -27,57 +25,37 @@ public sealed class ProviderOpenRouter() : BaseProvider(LLMProviders.OPEN_ROUTER
///
public override async IAsyncEnumerable StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{
- // Get the API key:
- var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
- if(!requestedSecret.Success)
- yield break;
+ await foreach (var content in this.StreamOpenAICompatibleChatCompletion(
+ "OpenRouter",
+ chatModel,
+ chatThread,
+ settingsManager,
+ async (systemPrompt, apiParameters) =>
+ {
+ // Build the list of messages:
+ var messages = await chatThread.Blocks.BuildMessagesUsingNestedImageUrlAsync(this.Provider, chatModel);
- // Prepare the system prompt:
- var systemPrompt = new TextMessage
- {
- Role = "system",
- Content = chatThread.PrepareSystemPrompt(settingsManager),
- };
+ return new ChatCompletionAPIRequest
+ {
+ Model = chatModel.Id,
- // Parse the API parameters:
- var apiParameters = this.ParseAdditionalApiParameters();
-
- // Build the list of messages:
- var messages = await chatThread.Blocks.BuildMessagesUsingNestedImageUrlAsync(this.Provider, chatModel);
+ // Build the messages:
+ // - First of all the system prompt
+ // - Then none-empty user and AI messages
+ Messages = [systemPrompt, ..messages],
- // Prepare the OpenRouter HTTP chat request:
- var openRouterChatRequest = JsonSerializer.Serialize(new ChatCompletionAPIRequest
- {
- Model = chatModel.Id,
-
- // Build the messages:
- // - First of all the system prompt
- // - Then none-empty user and AI messages
- Messages = [systemPrompt, ..messages],
-
- // Right now, we only support streaming completions:
- Stream = true,
- AdditionalApiParameters = apiParameters
- }, JSON_SERIALIZER_OPTIONS);
-
- async Task RequestBuilder()
- {
- // Build the HTTP post request:
- var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions");
-
- // Set the authorization header:
- request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION));
-
- // Set custom headers for project identification:
- request.Headers.Add("HTTP-Referer", PROJECT_WEBSITE);
- request.Headers.Add("X-Title", PROJECT_NAME);
-
- // Set the content:
- request.Content = new StringContent(openRouterChatRequest, Encoding.UTF8, "application/json");
- return request;
- }
-
- await foreach (var content in this.StreamChatCompletionInternal("OpenRouter", RequestBuilder, token))
+ // Right now, we only support streaming completions:
+ Stream = true,
+ AdditionalApiParameters = apiParameters
+ };
+ },
+ headersAction: headers =>
+ {
+ // Set custom headers for project identification:
+ headers.Add("HTTP-Referer", PROJECT_WEBSITE);
+ headers.Add("X-Title", PROJECT_NAME);
+ },
+ token: token))
yield return content;
}
diff --git a/app/MindWork AI Studio/Provider/Perplexity/ProviderPerplexity.cs b/app/MindWork AI Studio/Provider/Perplexity/ProviderPerplexity.cs
index 4c73dc2d..745dd974 100644
--- a/app/MindWork AI Studio/Provider/Perplexity/ProviderPerplexity.cs
+++ b/app/MindWork AI Studio/Provider/Perplexity/ProviderPerplexity.cs
@@ -1,7 +1,4 @@
-using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
-using System.Text;
-using System.Text.Json;
using AIStudio.Chat;
using AIStudio.Provider.OpenAI;
@@ -33,51 +30,29 @@ public sealed class ProviderPerplexity() : BaseProvider(LLMProviders.PERPLEXITY,
///
public override async IAsyncEnumerable StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{
- // Get the API key:
- var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
- if(!requestedSecret.Success)
- yield break;
-
- // Prepare the system prompt:
- var systemPrompt = new TextMessage
- {
- Role = "system",
- Content = chatThread.PrepareSystemPrompt(settingsManager),
- };
-
- // Parse the API parameters:
- var apiParameters = this.ParseAdditionalApiParameters();
-
- // Build the list of messages:
- var messages = await chatThread.Blocks.BuildMessagesUsingNestedImageUrlAsync(this.Provider, chatModel);
-
- // Prepare the Perplexity HTTP chat request:
- var perplexityChatRequest = JsonSerializer.Serialize(new ChatCompletionAPIRequest
- {
- Model = chatModel.Id,
-
- // Build the messages:
- // - First of all the system prompt
- // - Then none-empty user and AI messages
- Messages = [systemPrompt, ..messages],
- Stream = true,
- AdditionalApiParameters = apiParameters
- }, JSON_SERIALIZER_OPTIONS);
+ await foreach (var content in this.StreamOpenAICompatibleChatCompletion(
+ "Perplexity",
+ chatModel,
+ chatThread,
+ settingsManager,
+ async (systemPrompt, apiParameters) =>
+ {
+ // Build the list of messages:
+ var messages = await chatThread.Blocks.BuildMessagesUsingNestedImageUrlAsync(this.Provider, chatModel);
- async Task RequestBuilder()
- {
- // Build the HTTP post request:
- var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions");
+ return new ChatCompletionAPIRequest
+ {
+ Model = chatModel.Id,
- // Set the authorization header:
- request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION));
-
- // Set the content:
- request.Content = new StringContent(perplexityChatRequest, Encoding.UTF8, "application/json");
- return request;
- }
-
- await foreach (var content in this.StreamChatCompletionInternal("Perplexity", RequestBuilder, token))
+ // Build the messages:
+ // - First of all the system prompt
+ // - Then none-empty user and AI messages
+ Messages = [systemPrompt, ..messages],
+ Stream = true,
+ AdditionalApiParameters = apiParameters
+ };
+ },
+ token: token))
yield return content;
}
@@ -128,4 +103,4 @@ public sealed class ProviderPerplexity() : BaseProvider(LLMProviders.PERPLEXITY,
#endregion
private Task> LoadModels() => Task.FromResult>(KNOWN_MODELS);
-}
\ No newline at end of file
+}
diff --git a/app/MindWork AI Studio/Provider/SelfHosted/ChatRequest.cs b/app/MindWork AI Studio/Provider/SelfHosted/ChatRequest.cs
deleted file mode 100644
index e1da56bd..00000000
--- a/app/MindWork AI Studio/Provider/SelfHosted/ChatRequest.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System.Text.Json.Serialization;
-
-namespace AIStudio.Provider.SelfHosted;
-
-///
-/// The chat request model.
-///
-/// Which model to use for chat completion.
-/// The chat messages.
-/// Whether to stream the chat completion.
-public readonly record struct ChatRequest(
- string Model,
- IList Messages,
- bool Stream
-)
-{
- // Attention: The "required" modifier is not supported for [JsonExtensionData].
- [JsonExtensionData]
- public IDictionary AdditionalApiParameters { get; init; } = new Dictionary();
-}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs b/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs
index 8204fa6c..01e86cc3 100644
--- a/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs
+++ b/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs
@@ -1,7 +1,5 @@
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
-using System.Text;
-using System.Text.Json;
using AIStudio.Chat;
using AIStudio.Provider.OpenAI;
@@ -25,58 +23,39 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide
///
public override async IAsyncEnumerable StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{
- // Get the API key:
- var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER, isTrying: true);
-
- // Prepare the system prompt:
- var systemPrompt = new TextMessage
- {
- Role = "system",
- Content = chatThread.PrepareSystemPrompt(settingsManager),
- };
-
- // Parse the API parameters:
- var apiParameters = this.ParseAdditionalApiParameters();
+ await foreach (var content in this.StreamOpenAICompatibleChatCompletion(
+ "self-hosted provider",
+ chatModel,
+ chatThread,
+ settingsManager,
+ async (systemPrompt, apiParameters) =>
+ {
+ // Build the list of messages. The image format depends on the host:
+ // - Ollama uses the direct image URL format: { "type": "image_url", "image_url": "data:..." }
+ // - LM Studio, vLLM, and llama.cpp use the nested image URL format: { "type": "image_url", "image_url": { "url": "data:..." } }
+ var messages = host switch
+ {
+ Host.OLLAMA => await chatThread.Blocks.BuildMessagesUsingDirectImageUrlAsync(this.Provider, chatModel),
+ _ => await chatThread.Blocks.BuildMessagesUsingNestedImageUrlAsync(this.Provider, chatModel),
+ };
- // Build the list of messages. The image format depends on the host:
- // - Ollama uses the direct image URL format: { "type": "image_url", "image_url": "data:..." }
- // - LM Studio, vLLM, and llama.cpp use the nested image URL format: { "type": "image_url", "image_url": { "url": "data:..." } }
- var messages = host switch
- {
- Host.OLLAMA => await chatThread.Blocks.BuildMessagesUsingDirectImageUrlAsync(this.Provider, chatModel),
- _ => await chatThread.Blocks.BuildMessagesUsingNestedImageUrlAsync(this.Provider, chatModel),
- };
-
- // Prepare the OpenAI HTTP chat request:
- var providerChatRequest = JsonSerializer.Serialize(new ChatRequest
- {
- Model = chatModel.Id,
-
- // Build the messages:
- // - First of all the system prompt
- // - Then none-empty user and AI messages
- Messages = [systemPrompt, ..messages],
-
- // Right now, we only support streaming completions:
- Stream = true,
- AdditionalApiParameters = apiParameters
- }, JSON_SERIALIZER_OPTIONS);
+ return new ChatCompletionAPIRequest
+ {
+ Model = chatModel.Id,
- async Task RequestBuilder()
- {
- // Build the HTTP post request:
- var request = new HttpRequestMessage(HttpMethod.Post, host.ChatURL());
+ // Build the messages:
+ // - First of all the system prompt
+ // - Then none-empty user and AI messages
+ Messages = [systemPrompt, ..messages],
- // Set the authorization header:
- if (requestedSecret.Success)
- request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION));
-
- // Set the content:
- request.Content = new StringContent(providerChatRequest, Encoding.UTF8, "application/json");
- return request;
- }
-
- await foreach (var content in this.StreamChatCompletionInternal("self-hosted provider", RequestBuilder, token))
+ // Right now, we only support streaming completions:
+ Stream = true,
+ AdditionalApiParameters = apiParameters
+ };
+ },
+ isTryingSecret: true,
+ requestPath: host.ChatURL(),
+ token: token))
yield return content;
}
@@ -211,4 +190,4 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide
filterPhrases.All( filter => model.Id.Contains(filter, StringComparison.InvariantCulture)))
.Select(n => new Provider.Model(n.Id, null));
}
-}
\ No newline at end of file
+}
diff --git a/app/MindWork AI Studio/Provider/X/ProviderX.cs b/app/MindWork AI Studio/Provider/X/ProviderX.cs
index 21d6e2ca..8c1685ee 100644
--- a/app/MindWork AI Studio/Provider/X/ProviderX.cs
+++ b/app/MindWork AI Studio/Provider/X/ProviderX.cs
@@ -1,7 +1,5 @@
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
-using System.Text;
-using System.Text.Json;
using AIStudio.Chat;
using AIStudio.Provider.OpenAI;
@@ -24,53 +22,31 @@ public sealed class ProviderX() : BaseProvider(LLMProviders.X, "https://api.x.ai
///
public override async IAsyncEnumerable StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{
- // Get the API key:
- var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
- if(!requestedSecret.Success)
- yield break;
-
- // Prepare the system prompt:
- var systemPrompt = new TextMessage
- {
- Role = "system",
- Content = chatThread.PrepareSystemPrompt(settingsManager),
- };
-
- // Parse the API parameters:
- var apiParameters = this.ParseAdditionalApiParameters();
-
- // Build the list of messages:
- var messages = await chatThread.Blocks.BuildMessagesUsingNestedImageUrlAsync(this.Provider, chatModel);
-
- // Prepare the xAI HTTP chat request:
- var xChatRequest = JsonSerializer.Serialize(new ChatCompletionAPIRequest
- {
- Model = chatModel.Id,
-
- // Build the messages:
- // - First of all the system prompt
- // - Then none-empty user and AI messages
- Messages = [systemPrompt, ..messages],
-
- // Right now, we only support streaming completions:
- Stream = true,
- AdditionalApiParameters = apiParameters
- }, JSON_SERIALIZER_OPTIONS);
+ await foreach (var content in this.StreamOpenAICompatibleChatCompletion(
+ "xAI",
+ chatModel,
+ chatThread,
+ settingsManager,
+ async (systemPrompt, apiParameters) =>
+ {
+ // Build the list of messages:
+ var messages = await chatThread.Blocks.BuildMessagesUsingNestedImageUrlAsync(this.Provider, chatModel);
- async Task RequestBuilder()
- {
- // Build the HTTP post request:
- var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions");
+ return new ChatCompletionAPIRequest
+ {
+ Model = chatModel.Id,
- // Set the authorization header:
- request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION));
+ // Build the messages:
+ // - First of all the system prompt
+ // - Then none-empty user and AI messages
+ Messages = [systemPrompt, ..messages],
- // Set the content:
- request.Content = new StringContent(xChatRequest, Encoding.UTF8, "application/json");
- return request;
- }
-
- await foreach (var content in this.StreamChatCompletionInternal("xAI", RequestBuilder, token))
+ // Right now, we only support streaming completions:
+ Stream = true,
+ AdditionalApiParameters = apiParameters
+ };
+ },
+ token: token))
yield return content;
}
@@ -158,4 +134,4 @@ public sealed class ProviderX() : BaseProvider(LLMProviders.X, "https://api.x.ai
}
]);
}
-}
\ No newline at end of file
+}