Vergleiche OpenAI-Modelle Side-by-Side mit Blazor Server
Lesedauer: 4 Minuten

Wenn du regelmäßig mit verschiedenen OpenAI-Modellen arbeitest, taucht sehr schnell eine Frage auf:
„Welches Modell sollte ich für diese Aufgabe benutzen?“ Latenz, Output-Qualität, Tokenverbrauch und Preis spielen alle eine Rolle — und meistens siehst du die echten Unterschiede erst dann, wenn du die Modelle nebeneinander ausprobierst. Also habe ich ein kleines Blazor-Server-Dashboard gebaut, das genau das macht: Ein Prompt rein → mehrere Modelle raus → Ergebnisse sauber nebeneinander.

Live-Demo: https://openaimodelcomparison.tsjdev-apps.de

🚀 Warum Blazor Server?

Ich wollte eine simple UI, ohne tonnenweise Client-Side-JavaScript.
Blazor Server hält eine persistente SignalR-Verbindung und lässt den Server die ganze Arbeit übernehmen:

  • Parallele API-Calls
  • Messung der Antwortzeiten
  • Tokenzählung
  • Kostenberechnung

Der Browser bleibt also schön leichtgewichtig — so wie es sein sollte.

Stack: .NET 10 + Radzen für schnellen UI-Aufbau.

⚙️ Konfiguration über JSON

Welche Modelle getestet werden und wie viel sie kosten, liegt in einer kleinen JSON-Datei. Preise sind angegeben in „per million tokens“, ganz wie im offiziellen OpenAI-Pricing.

JSON
[
  {
    "name": "gpt-5.1",
    "displayName": "GPT-5.1",
    "isSelected": false,
    "inputPricePerMillionTokens": 1.25,
    "outputPricePerMillionTokens": 10.0
  },
  {
    "name": "gpt-4.1-mini",
    "displayName": "GPT-4.1 mini",
    "isSelected": false,
    "inputPricePerMillionTokens": 0.40,
    "outputPricePerMillionTokens": 1.60
  }
]
JSON

Beim Start wandelt ein Konfigurationsservice die Datei in starke Typen um. Der Rest der App bleibt super clean — keine Dateien, kein JSON, nur Models.

🧱 Minimaler Startup-Code

Die Program.cs ist bewusst kompakt gehalten: Services registrieren, Razor Components mappen, fertig.

C#
// Application entry point and service configuration
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

// Add Radzen Blazor components for UI
builder.Services.AddRadzenComponents();

// Add Razor components with interactive server-side rendering
builder.Services
    .AddRazorComponents()
    .AddInteractiveServerComponents();

// Register application services
builder.Services
  .AddSingleton<IModelConfigurationService, ModelConfigurationService>();
builder.Services
  .AddScoped<ISamplePromptService, SamplePromptService>();
builder.Services
  .AddScoped<IOpenAIComparisonService, OpenAIComparisonService>();

WebApplication app = builder.Build();

// Map static assets and Razor components with interactive server rendering
app.MapStaticAssets();
app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();

// Start the application
app.Run();
C#

⚡ Fan-Out: Ein Prompt → Viele Modelle

Dieser Service nimmt einen Prompt, schickt ihn an mehrere Modelle, wartet alle Antworten ab und liefert Latenz, Token-Stats und geschätzte Kosten zurück.

C#
/// <summary>
/// Gets a response from a specific OpenAI model for the given usermessage.
/// </summary>
/// <param name="apiKey">The OpenAI API key for authentication.<param>
/// <param name="userMessage">The message to send to the model.<param>
/// <param name="model">The model to query.</param>
/// <param name="settings">Optional chat settings to customize 
///                        theAPI request.</param>
/// <returns>A comparison result containing the model's 
///          responseand metrics.</returns>
private async Task<ModelComparisonResult> GetModelResponseAsync(
    string apiKey,
    string userMessage,
    OpenAIModel model,
    ChatSettings? settings)
{
    await _throttler.WaitAsync();
    Stopwatch stopwatch = Stopwatch.StartNew();
    
    try
    {
        OpenAIClient client = new(apiKey);
        ChatClient chatClient = client.GetChatClient(model.Name);

        List<ChatMessage> messages =
        [
            new SystemChatMessage("You are a helpful assistant."),
            new UserChatMessage(userMessage)
        ];

        OpenAI.Chat.ChatCompletionOptions chatOptions = new()
        {
            MaxOutputTokenCount = settings?.MaxTokens,
            Temperature = settings?.Temperature,
            TopP = settings?.TopP,
            FrequencyPenalty = settings?.FrequencyPenalty
        };

        ChatCompletion response = await chatClient
            .CompleteChatAsync(messages, chatOptions);
        stopwatch.Stop();

        string content = response.Content[0].Text;
        
        int inputTokens = response.Usage.InputTokenCount;
        int outputTokens = response.Usage.OutputTokenCount;
        
        double inputCost = (inputTokens / 1_000_000.0) 
            * model.InputPricePerMillionTokens;
        double outputCost = (outputTokens / 1_000_000.0) 
            * model.OutputPricePerMillionTokens;
        double totalCost = inputCost + outputCost;

        return new ModelComparisonResult
        {
            ModelName = model.Name,
            ModelDisplayName = model.DisplayName,
            Response = content,
            InputTokens = inputTokens,
            OutputTokens = outputTokens,
            TotalTokens = response.Usage.TotalTokenCount,
            DurationInSeconds = stopwatch.Elapsed.TotalSeconds,
            EstimatedCost = totalCost,
            IsSuccess = true
        };
    }
    catch (Exception ex)
    {
        stopwatch.Stop();
        
        return new ModelComparisonResult
        {
            ModelName = model.Name,
            ModelDisplayName = model.DisplayName,
            Response = string.Empty,
            InputTokens = 0,
            OutputTokens = 0,
            TotalTokens = 0,
            DurationInSeconds = stopwatch.Elapsed.TotalSeconds,
            EstimatedCost = 0,
            IsSuccess = false,
            ErrorMessage = ex.Message
        };
    }
    finally
    {
        _throttler.Release();
    }
}
C#

Zwei Dinge machen den Ansatz extrem nützlich:

  1. Stopwatch pro Anfrage
    → ehrliche Latenzwerte, nicht nur Serververarbeitungszeit.
  2. Neutrales DTO
    → Die UI bleibt angenehm simpel und rein darstellend.

🖥️ Eine einfache, ehrliche UI

Das Ziel war nicht, den „UI-Preis des Jahres“ zu gewinnen, sondern Reibung zu entfernen: Modelle auswählen → Prompt eingeben → Button drücken → Antworten vergleichen.

🛡️ Resilienz & Limits

Parallele Calls sind super — bis die Rate Limits zuschlagen.

Für produktive Nutzung solltest du:

  • Retry mit Jitter einbauen
  • Concurrency throttlen, wenn viele Modelle ausgewählt sind
  • CancellationTokens durchreichen (Blazor Server liefert automatisch eins pro Anfrage)

Das schützt dich davor, dass ein Nutzer wegklickt und dein Server trotzdem wie wild weiterarbeitet.

🎯 Probier’s selbst

Live: https://openaimodelcomparison.tsjdev-apps.de

  • Bring einfach deinen eigenen OpenAI API-Key mit.
  • Repo klonen, models.json anpassen
  • Zuschauen, wie Preis- und Verhaltensänderungen sofort sichtbar werden.

✨ Fazit

Das Dashboard ist klein, aber unglaublich hilfreich beim Experimentieren mit LLMs. Ob du Prompts tunst, Kosten einschätzt oder Latenz vergleichst — alle Antworten nebeneinander zu sehen liefert Einsichten, die du bei Einzelabfragen nie bekommst.

Abkürzungen der Künstlichen Intelligenz KI-Videos mit Sora-2 und .NET erstellen Ollama mit neuer UI – Einfacher Zugriff auf lokale KI-Modelle
View Comments
There are currently no comments.