Zurück zur Startseite

YandexGPT-Integration in .NET — Leitfaden

Leitfaden zur Integration von YandexGPT in .NET-Projekte. Umfasst sichere Client-Initialisierung, asynchrone Aufgabenverarbeitung mit exponentiellem Backoff, Validierung und Speicherung von Bildern, Markdown-Konvertierung zu sicherem HTML. Ziel — produktionsreife Lösung für Entwickler.

Wie verbindet man YandexGPT korrekt mit einem .NET-Projekt
Advertisement 728x90

YandexGPT-Integration in .NET: Ein praktischer Leitfaden für Entwickler

YandexGPT ist nicht nur ein Cloud-LLM-Dienst; es ist eine vollständig verwaltete API, die asynchrone Text- und Bildgenerierung unterstützt und sich nahtlos in die Unternehmensinfrastruktur von .NET integriert. Dieser Artikel bietet eine produktionsreife Implementierung der Integration und geht auf reale Herausforderungen wie Timeouts, Betriebsstatus, Base64-Behandlung und Markdown-Ausgabe-Sanitisierung ein. Wir konzentrieren uns auf das, was in Produktionsumgebungen am wichtigsten ist: Fehlertoleranz, Typsicherheit, Kontrolle über Prompt-Engineering und sichere Speicherung von Anmeldedaten.

Client-Architektur und sichere Initialisierung

Die Klasse MyGPTClient muss threadsicher sein und Ressourcenlecks vermeiden. Die Verwendung eines globalen HttpClient ist ein Anti-Pattern. Stattdessen nutzen wir IHttpClientFactory, registriert über Dependency Injection:

// Program.cs
builder.Services.AddHttpClient<MyGPTClient>((sp, client) =>
{
    var config = sp.GetRequiredService<IConfiguration>();
    client.BaseAddress = new Uri("https://llm.api.cloud.yandex.net/");
    client.DefaultRequestHeaders.Authorization = 
        new System.Net.Http.Headers.AuthenticationHeaderValue(
            "Api-Key", config["Yandex:ApiKey"]);
});

Der Konstruktor von MyGPTClient akzeptiert nun IHttpClientFactory statt direkt einen HttpClient zu erstellen. Modell-URIs (gpt://.../yandexgpt, art://.../yandex-art/latest) werden in die Konfiguration ausgelagert, sodass ein einfacher Wechsel zwischen Umgebungen (Dev/Staging/Prod) ohne Neukompilierung möglich ist.

Google AdInline article slot

Wichtig: Die Rolle ai.languageModels.user erfordert eine explizite Zuweisung in der IAM-Richtlinie des Yandex Cloud-Katalogs. Für CI/CD-Umgebungen ist ein separates Dienstkonto mit minimalen Berechtigungen – nur ai.*.user – erforderlich. Keine editor oder admin Rollen.

Asynchrone Task-Handhabung: Vom Polling bis zur Retry-Logik

Die ursprüngliche Methode WaitForTaskCompletionAsync verwendet eine Endlosschleife mit einer festen Task.Delay(1000). Das ist in der Produktion inakzeptabel: Bei über 50 gleichzeitigen Anfragen belastet es die API-Limits und erschöpft die Threads. Wir implementieren adaptives Polling mit exponentieller Backoff-Strategie und Retry-Grenzen:

private async Task<dynamic> WaitForTaskCompletionAsync(string taskId, int maxAttempts = 30)
{
    int attempt = 0;
    TimeSpan delay = TimeSpan.FromMilliseconds(500);

    while (attempt < maxAttempts)
    {
        try
        {
            var response = await _client.GetAsync($"operations/{taskId}");
            response.EnsureSuccessStatusCode();

            var responseBody = await response.Content.ReadAsStringAsync();
            var result = JsonConvert.DeserializeObject<dynamic>(responseBody);

            if (result?.done == true && result?.response != null)
                return result.response;

            if (result?.error != null)
                throw new InvalidOperationException($"API-Fehler: {result.error.message}");
        }
        catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
        {
            // Behandle 429 – erhöhe die Verzögerung
            delay = TimeSpan.FromMilliseconds(Math.Min(delay.TotalMilliseconds * 1.5, 10_000));
        }
        catch (JsonException)
        {
            throw new InvalidOperationException("Ungültiges JSON im Task-Status-Response");
        }

        await Task.Delay(delay);
        attempt++;
        delay = TimeSpan.FromMilliseconds(Math.Min(delay.TotalMilliseconds * 1.2, 5_000));
    }

    throw new TimeoutException($"Task {taskId} wurde innerhalb von {maxAttempts} Versuchen nicht abgeschlossen");
}

Diese Lösung adressiert zwei zentrale Probleme: Erstens verhindert sie API-Überlastung bei vorübergehender Nichtverfügbarkeit; zweitens löst sie bei einem Timeout eine klare Ausnahme aus – essentiell für korrektes Logging und Alarmierung.

Google AdInline article slot

Ausgabe-Sanitisierung: Von Markdown zu semantischem HTML

YandexGPT gibt Markdown mit inkonsistenter Formatierung zurück: #, ##, *, , sowie zufällige Zeichen (////, ----, ****). Einfache Regex-Ersetzungen reichen nicht aus. Wir implementieren einen Parser basierend auf Markdig, einer Bibliothek, die eine sichere Konvertierung im Whitelist-Modus unterstützt:

public string ConvertToHtml(string markdown)
{
    if (string.IsNullOrWhiteSpace(markdown)) return string.Empty;

    var pipeline = new MarkdownPipelineBuilder()
        .UseAdvancedExtensions()
        .UseEmojiAndSmiley()
        .Build();

    // Entferne Überschriften der ersten Ebene – sie duplizieren das Thema des Artikels
    var cleaned = Regex.Replace(markdown, @"^#{1,6}\s+.*$", "", RegexOptions.Multiline | RegexOptions.IgnoreCase);

    // Deaktiviere gefährliche Elemente
    var html = Markdown.ToHtml(cleaned, pipeline);
    return SanitizeHtml(html); // implementiert via HtmlSanitizer
}

private string SanitizeHtml(string html)
{
    var sanitizer = new HtmlSanitizer();
    sanitizer.AllowedTags.Clear();
    sanitizer.AllowedTags.UnionWith(new[] { "p", "br", "strong", "em", "ul", "ol", "li", "h2", "h3" });
    sanitizer.AllowedAttributes.Clear();
    return sanitizer.Sanitize(html);
}

Dies stellt sicher, dass die Ausgabe gültiges, sicheres HTML ohne <script>, <iframe> oder Inline-Stile ist.

Bildgenerierung: Base64-Validierung und Speicherung

Die Methode GenerateImageAsync erfordert strenge Validierung von MIME-Typ und Größe. Base64-Strings können beschädigt sein oder nicht-JPEG-Daten enthalten. Wir fügen Validierung hinzu:

Google AdInline article slot
private bool IsValidBase64Image(string base64String)
{
    if (!base64String.StartsWith("data:image/", StringComparison.OrdinalIgnoreCase) ||
        !base64String.Contains(";base64,"))
        return false;

    var data = base64String.Substring(base64String.IndexOf(',') + 1);
    if (!IsValidBase64String(data)) return false;

    try
    {
        var bytes = Convert.FromBase64String(data);
        using var ms = new MemoryStream(bytes);
        using var img = Image.Load(ms);
        return img.Metadata.DecodedImageOrientation == ExifOrientation.Undefined ||
               img.Width > 100 && img.Height > 100;
    }
    catch
    {
        return false;
    }
}

Das Speichern erfolgt in IWebHostEnvironment.WebRootPath/Resources/ArticleImg – dem Standardpfad für statische Dateien in ASP.NET Core. Dateinamen werden mit Path.GetRandomFileName() und .jpg-Erweiterung generiert, statt Guid.NewGuid(), um Kollisionen unter hoher Last zu vermeiden.

Wichtige Erkenntnisse

  • Sicherheit: API-Schlüssel werden über IConfiguration übergeben, nicht hardcodiert. Dienstkonten haben nur ai.*.user Rollen.
  • Zuverlässigkeit: Polling wird mit exponentiellem Backoff und Retry-Grenzen implementiert. Alle HTTP-Fehler werden explizit behandelt.
  • Kompatibilität: Der Client nutzt IHttpClientFactory, was Polly für Retries und Circuit Breakers ermöglicht.
  • Ausgabe-Reinheit: Markdown-zu-HTML-Konvertierung erfolgt über Markdig und HtmlSanitizer, nicht über Regex-Ersetzungen.
  • Performance: Bilder werden im WebRootPath gespeichert, zugänglich über URL ohne Middleware.

Für Tests empfiehlt es sich, einen Mock-Server (z. B. WireMock.NET) zu verwenden, der /foundationModels/v1/completionAsync und /operations/{id} emuliert. Damit werden alle Zustände abgedeckt: done: true, done: false, error, 429 Too Many Requests.

Der vollständige Code, einschließlich DI-Registrierung, Unit-Tests und einem Controller-Nutzungsbeispiel, ist im dotnet-yandexgpt-sample-Repository verfügbar. In der Produktion sollte unbedingt Application Insights für das Tracing von Aufrufen an die Yandex-API konfiguriert werden – es ist entscheidend für die Diagnose von Verzögerungen und Fehlern.

— Editorial Team

Advertisement 728x90

Weiterlesen