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.
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.
Č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:
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 roleai.*.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
MarkdigaHtmlSanitizer, 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
Zatím žádné komentáře.