From 955f212c57f7edaa2ef1101379b4286a3d64b126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peer=20Sch=C3=BCtt?= <20603780+peerschuett@users.noreply.github.com> Date: Thu, 23 Oct 2025 09:40:36 +0200 Subject: [PATCH] Standardized `AdditionalApiParameters` type across providers to use `IDictionary` and improved JSON parsing reliability. --- .../Dialogs/ProviderDialog.razor | 2 +- .../Provider/Anthropic/ChatRequest.cs | 3 +- .../Provider/Anthropic/ProviderAnthropic.cs | 2 +- .../Provider/BaseProvider.cs | 59 +++++++++---------- .../Provider/Fireworks/ChatRequest.cs | 3 +- .../Provider/Google/ChatRequest.cs | 4 +- .../Provider/Groq/ChatRequest.cs | 3 +- .../Provider/Mistral/ChatRequest.cs | 3 +- .../Provider/Mistral/ProviderMistral.cs | 2 +- .../OpenAI/ChatCompletionAPIRequest.cs | 3 +- .../Provider/OpenAI/ResponsesAPIRequest.cs | 3 +- .../Provider/SelfHosted/ChatRequest.cs | 3 +- 12 files changed, 39 insertions(+), 51 deletions(-) diff --git a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor index f465c5cf..a0cf55f4 100644 --- a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor +++ b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor @@ -135,7 +135,7 @@ @T("Please be aware: This is for experts only. You are responsible for verifying the correctness of the additional parameters you provide to the API call.") - @T("By default, AI Studio uses the OpenAI-compatible chat/completions API, provided it is supported by the underlying service.") + @T("By default, AI Studio uses the OpenAI-compatible chat/completions API, provided it is supported by the underlying service and model.") diff --git a/app/MindWork AI Studio/Provider/Anthropic/ChatRequest.cs b/app/MindWork AI Studio/Provider/Anthropic/ChatRequest.cs index afaef92c..3a9404b9 100644 --- a/app/MindWork AI Studio/Provider/Anthropic/ChatRequest.cs +++ b/app/MindWork AI Studio/Provider/Anthropic/ChatRequest.cs @@ -20,6 +20,5 @@ public readonly record struct ChatRequest( ) { -[JsonExtensionData] -public Dictionary AdditionalApiParameters { get; init; } +public IDictionary AdditionalApiParameters { get; init; } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Provider/Anthropic/ProviderAnthropic.cs b/app/MindWork AI Studio/Provider/Anthropic/ProviderAnthropic.cs index 9a7dd054..298f54d8 100644 --- a/app/MindWork AI Studio/Provider/Anthropic/ProviderAnthropic.cs +++ b/app/MindWork AI Studio/Provider/Anthropic/ProviderAnthropic.cs @@ -58,7 +58,7 @@ public sealed class ProviderAnthropic() : BaseProvider("https://api.anthropic.co }).ToList()], System = chatThread.PrepareSystemPrompt(settingsManager, chatThread), - MaxTokens = apiParameters["max_tokens"] as int? ?? 4_096, + MaxTokens = int.TryParse(apiParameters["max_tokens"], out int parsed) ? parsed : 4_096, // Right now, we only support streaming completions: Stream = true, diff --git a/app/MindWork AI Studio/Provider/BaseProvider.cs b/app/MindWork AI Studio/Provider/BaseProvider.cs index 87248bae..e89f711d 100644 --- a/app/MindWork AI Studio/Provider/BaseProvider.cs +++ b/app/MindWork AI Studio/Provider/BaseProvider.cs @@ -530,49 +530,46 @@ public abstract class BaseProvider : IProvider, ISecretId /// optionally merging additional parameters and removing specific keys. /// /// A JSON string (without surrounding braces) containing the API parameters to be parsed. - /// Optional additional parameters to merge into the result. These will overwrite existing keys. /// Optional list of keys to remove from the final dictionary (case-insensitive). stream, model and messages are removed by default. - protected Dictionary ParseApiParameters( + protected IDictionary ParseApiParameters( string additionalUserProvidedParameters, IEnumerable? keysToRemove = null) { - - var json = $"{{{additionalUserProvidedParameters}}}"; - var jsonDoc = JsonSerializer.Deserialize(json, JSON_SERIALIZER_OPTIONS); - var dict = this.ConvertToDictionary(jsonDoc); - - // Some keys are always removed because we always set them - var finalKeysToRemove = keysToRemove?.ToList() ?? new List(); - finalKeysToRemove.Add("stream"); - finalKeysToRemove.Add("model"); - finalKeysToRemove.Add("messages"); + try + { + // we need to remove line breaks from the JSON string otherwise the server might have problems with parsing the call + var withoutLineBreak = additionalUserProvidedParameters.Replace("\n", string.Empty); + + var json = $"{{{withoutLineBreak}}}"; + var jsonDoc = JsonSerializer.Deserialize(json, JSON_SERIALIZER_OPTIONS); + var dict = this.ConvertToDictionary(jsonDoc); - var removeSet = new HashSet(finalKeysToRemove, StringComparer.OrdinalIgnoreCase); - var toRemove = dict.Keys.Where(k => removeSet.Contains(k)).ToList(); - foreach (var k in toRemove) - dict.Remove(k); + // Some keys are always removed because we always set them + var finalKeysToRemove = keysToRemove?.ToList() ?? new List(); + finalKeysToRemove.Add("stream"); + finalKeysToRemove.Add("model"); + finalKeysToRemove.Add("messages"); - return dict; + var removeSet = new HashSet(finalKeysToRemove, StringComparer.OrdinalIgnoreCase); + var toRemove = dict.Keys.Where(k => removeSet.Contains(k)).ToList(); + foreach (var k in toRemove) + dict.Remove(k); + + return dict; + } + catch (JsonException ex) + { + throw new ArgumentException("Invalid JSON in additionalUserProvidedParameters", ex); + } } - private Dictionary ConvertToDictionary(JsonElement element) + private IDictionary ConvertToDictionary(JsonElement element) { return element.EnumerateObject() .ToDictionary( p => p.Name, - p => this.ConvertJsonElement(p.Value) + p => p.Value.GetRawText() ); } - - private object? ConvertJsonElement(JsonElement element) => element.ValueKind switch - { - JsonValueKind.String => element.GetString(), - JsonValueKind.Number => element.TryGetInt32(out var i) ? (object)i : element.GetDouble(), - JsonValueKind.True => true, - JsonValueKind.False => false, - JsonValueKind.Object => this.ConvertToDictionary(element), - JsonValueKind.Array => element.EnumerateArray().Select(this.ConvertJsonElement).ToList(), - JsonValueKind.Null => null, - _ => element.ToString() - }; + } \ 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 index cf9f5115..0738ea32 100644 --- a/app/MindWork AI Studio/Provider/Fireworks/ChatRequest.cs +++ b/app/MindWork AI Studio/Provider/Fireworks/ChatRequest.cs @@ -15,6 +15,5 @@ public readonly record struct ChatRequest( ) { - [JsonExtensionData] - public Dictionary AdditionalApiParameters { get; init; } + public IDictionary AdditionalApiParameters { get; init; } } \ 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 index e0b50874..b8e1d2ed 100644 --- a/app/MindWork AI Studio/Provider/Google/ChatRequest.cs +++ b/app/MindWork AI Studio/Provider/Google/ChatRequest.cs @@ -15,7 +15,5 @@ public readonly record struct ChatRequest( bool Stream ) { - - [JsonExtensionData] - public Dictionary AdditionalApiParameters { get; init; } + public IDictionary AdditionalApiParameters { get; init; } } \ 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 50d320bc..2725eae0 100644 --- a/app/MindWork AI Studio/Provider/Groq/ChatRequest.cs +++ b/app/MindWork AI Studio/Provider/Groq/ChatRequest.cs @@ -18,6 +18,5 @@ public readonly record struct ChatRequest( ) { - [JsonExtensionData] - public Dictionary AdditionalApiParameters { get; init; } + public IDictionary AdditionalApiParameters { get; init; } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Provider/Mistral/ChatRequest.cs b/app/MindWork AI Studio/Provider/Mistral/ChatRequest.cs index 48d69bb0..0daaebf7 100644 --- a/app/MindWork AI Studio/Provider/Mistral/ChatRequest.cs +++ b/app/MindWork AI Studio/Provider/Mistral/ChatRequest.cs @@ -19,6 +19,5 @@ public readonly record struct ChatRequest( ) { - [JsonExtensionData] - public Dictionary AdditionalApiParameters { get; init; } + public IDictionary AdditionalApiParameters { get; init; } } \ 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 2b28487c..13e35a08 100644 --- a/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs +++ b/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs @@ -69,7 +69,7 @@ public sealed class ProviderMistral() : BaseProvider("https://api.mistral.ai/v1/ // Right now, we only support streaming completions: Stream = true, - SafePrompt = apiParameters["safe_prompt"] as bool? ?? false, + SafePrompt = bool.TryParse(apiParameters["safe_prompt"], out bool safePrompt) && safePrompt, AdditionalApiParameters = apiParameters }, JSON_SERIALIZER_OPTIONS); diff --git a/app/MindWork AI Studio/Provider/OpenAI/ChatCompletionAPIRequest.cs b/app/MindWork AI Studio/Provider/OpenAI/ChatCompletionAPIRequest.cs index d5a30286..1d79950e 100644 --- a/app/MindWork AI Studio/Provider/OpenAI/ChatCompletionAPIRequest.cs +++ b/app/MindWork AI Studio/Provider/OpenAI/ChatCompletionAPIRequest.cs @@ -18,6 +18,5 @@ public record ChatCompletionAPIRequest( { } - [JsonExtensionData] - public Dictionary? AdditionalApiParameters { get; init; } + public IDictionary? AdditionalApiParameters { get; init; } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Provider/OpenAI/ResponsesAPIRequest.cs b/app/MindWork AI Studio/Provider/OpenAI/ResponsesAPIRequest.cs index 520e50f4..ee399e47 100644 --- a/app/MindWork AI Studio/Provider/OpenAI/ResponsesAPIRequest.cs +++ b/app/MindWork AI Studio/Provider/OpenAI/ResponsesAPIRequest.cs @@ -21,6 +21,5 @@ public record ResponsesAPIRequest( { } - [JsonExtensionData] - public Dictionary? AdditionalApiParameters { get; init; } + public IDictionary? AdditionalApiParameters { get; init; } } \ 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 index 97618492..8599f72c 100644 --- a/app/MindWork AI Studio/Provider/SelfHosted/ChatRequest.cs +++ b/app/MindWork AI Studio/Provider/SelfHosted/ChatRequest.cs @@ -15,6 +15,5 @@ public readonly record struct ChatRequest( ) { - [JsonExtensionData] - public Dictionary AdditionalApiParameters { get; init; } + public IDictionary AdditionalApiParameters { get; init; } } \ No newline at end of file