Zpět na domů

Integrace YandexGPT do .NET — průvodce

Průvodce integrací YandexGPT do projektů .NET. Popsána bezpečná inicializace klienta, asynchronní zpracování úkolů s exponenciálním backoff, validace a ukládání obrázků, konverze Markdown do bezpečného HTML. Cíl — production-ready řešení pro vývojáře.

Jak správně připojit YandexGPT k projektu .NET
Advertisement 728x90

Integrace YandexGPT do .NET: praktický návod pro vývojáře

YandexGPT není jen cloudový LLM-servis, ale plně spravované API s podporou asynchronní generace textu a obrázků, kompatibilní s enterprise-infrastrukturou .NET. V tomto článku najdete pracovní implementaci integrace bez abstrakcí, s ohledem na reálné problémy: timeouty, stavové informace o operacích, zpracování base64 a čištění Markdown-výstupu. Zaměřujeme se na to, co je pro produkci kriticky důležité: odolnost proti selhání, typová bezpečnost, kontrola nad prompt-engineeringem a bezpečné uchovávání přihlašovacích údajů.

Architektura klienta a bezpečná inicializace

Třída MyGPTClient musí být thread-safe a zamezit únikům zdrojů. Používání globálního HttpClient je antipattern. Místo toho použijeme IHttpClientFactory, registrovaný prostřednictvím DI:

// 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"]);
});

Konstruktor MyGPTClient nyní přijímá IHttpClientFactory, místo aby vytvářel HttpClient přímo. URI modelů (gpt://.../yandexgpt, art://.../yandex-art/latest) jsou umístěny v konfiguraci — to umožňuje snadno přepínat mezi prostředími (dev/staging/prod) bez nutnosti překompilace.

Google AdInline article slot

Důležité: role ai.languageModels.user vyžaduje explicitní přiřazení v IAM politice katalogu Yandex Cloud. Pro CI/CD prostředí je třeba samostatný servisní účet s minimálními právy — pouze ai.*.user. Žádné editor ani admin.

Asynchronní zpracování úloh: od pollingu po retry-logiku

Metoda WaitForTaskCompletionAsync v původní verzi používá nekonečný cyklus s fixním Task.Delay(1000). To je v produkci nepřijatelné: při více než 50 současných požadavcích dochází k zatížení limitů API a vyčerpávání vláken. Implementujeme adaptivní polling s exponenciální prodlevou a omezením počtu pokusů:

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($"Chyba API: {result.error.message}");
        }
        catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
        {
            // Zpracování 429 — zvyšujeme prodlevu
            delay = TimeSpan.FromMilliseconds(Math.Min(delay.TotalMilliseconds * 1.5, 10_000));
        }
        catch (JsonException)
        {
            throw new InvalidOperationException("Neplatný JSON ve stavové odpovědi úlohy");
        }

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

    throw new TimeoutException($"Úloha {taskId} se nedokončila během {maxAttempts} pokusů");
}

Toto řeší dvě problémy: zaprvé, předejde floodu API při dočasné nedostupnosti; zadruhé, poskytuje jasnou výjimku při timeoutu — bez ní není možné korektně logovat a upozorňovat.

Google AdInline article slot

Čištění výstupu: od Markdownu k semantickému HTML

YandexGPT vrátí Markdown s nekonzistentním formátováním: #, ##, *, , ale také náhodné symboly (////, ----, ****). Jednoduchá substituce pomocí regulárek nestačí. Implementujeme parser na základě Markdig — knihovny, která podporuje bezpečnou konverzi v režimu whitelist:

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

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

    // Odstraníme nadpisy prvního stupně — ty duplikují téma článku
    var cleaned = Regex.Replace(markdown, @"^#{1,6}\s+.*$", "", RegexOptions.Multiline | RegexOptions.IgnoreCase);

    // Vypneme nebezpečné prvky
    var html = Markdown.ToHtml(cleaned, pipeline);
    return SanitizeHtml(html); // realizace prostřednictvím 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);
}

Toto zaručuje, že na výstupu bude platný, bezpečný HTML bez <script>, <iframe> nebo inline stylů.

Generace obrázků: validace base64 a ukládání

Metoda GenerateImageAsync vyžaduje striktní kontrolu MIME typu a velikosti. Base64 řetězec může být poškozený nebo obsahovat jiný formát než JPEG. Přidáváme validaci:

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;
    }
}

Ukládání probíhá v IWebHostEnvironment.WebRootPath/Resources/ArticleImg — standardní cesta pro statické soubory v ASP.NET Core. Jméno souboru se generuje pomocí Path.GetRandomFileName() s příponou .jpg, nikoli Guid.NewGuid(), aby se předešlo kolizím při vysokém zatížení.

Co je důležité

  • Bezpečnost: API klíče se předávají prostřednictvím IConfiguration, nikoli hardcodováním. Servisní účet má pouze role ai.*.user.
  • Spolehlivost: Polling je implementován s exponenciální prodlevou a limitem pokusů. Všechny HTTP chyby jsou explicitně zpracovány.
  • Kompatibilita: Klient používá IHttpClientFactory, což umožňuje aplikovat Polly pro retry a circuit breaker.
  • Čistota výstupu: Konverze Markdown → HTML probíhá prostřednictvím Markdig a HtmlSanitizer, nikoli regex substitucí.
  • Výkon: Obrázky se ukládají v WebRootPath, dostupné pod URL bez middleware.

Pro testování se doporučuje použít mock server (například WireMock.NET), emulující /foundationModels/v1/completionAsync a /operations/{id}. To umožňuje pokrýt všechny stavy: done: true, done: false, error, 429 Too Many Requests.

Celý kód s DI registrací, unit-testy a příkladem použití v controlleru je dostupný v repozitáři dotnet-yandexgpt-sample. V produkci je nutné nastavit Application Insights pro tracování volání k Yandex API — to je kriticky důležité pro diagnostiku zpoždění a chyb.

— Editorial Team

Advertisement 728x90

Číst dál