Лого на 91. НЕГ „Проф. Константин Гълъбов“

Избираем модул · Урок 19

Интеграция в C# приложение

Извикване на модела от C#: HttpClient, асинхронност, разчитане на JSON, съхранение на ключа и свързване с базата през ADO.NET.

Защо C#

Проектите ви от модул 4 са на C# и работят с база от данни. AI функцията не изисква Python — тя е обикновено HTTP извикване, което се прави еднакво добре от всеки език.

Разделение на труда

Python остава удобен за проучване, обучение на модели и измервания. За приложението, което ползва готова услуга, езикът е този на самото приложение.

Ключът в C# приложение

КъдеПодходящо ли еЗащо
В коданевлиза в хранилището и в компилирания файл
В App.config до exe-тонепотребителят го отваря с текстов редактор
Променлива на средата на сървърадане пътува с приложението
Собствен сървър между клиента и моделанай-добренастолното приложение изобщо не вижда ключа

Настолно приложение = ключът е у потребителя

Всичко, което инсталирате на чужда машина, може да бъде прочетено. Ако приложението е настолно, ключът стои на ваш сървър, а приложението вика него — точно както в урок 15.

Извикването

Един HttpClient, зададен веднъж, а не нов за всяка заявка. Класическа грешка в C# е създаването на нов клиент при всяко извикване — води до изчерпване на портовете.

using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;

public sealed class ModelKlient
{
    private static readonly HttpClient http = new HttpClient
    {
        Timeout = TimeSpan.FromSeconds(30)
    };

    private readonly string klyuch;

    public ModelKlient(string klyuch) => this.klyuch = klyuch;

    public async Task<string> PitayAsync(string sistemno, string vapros)
    {
        var zaqvka = new
        {
            system_instruction = new { parts = new[] { new { text = sistemno } } },
            contents = new[]
            {
                new { role = "user", parts = new[] { new { text = vapros } } }
            },
            generationConfig = new { temperature = 0.2, maxOutputTokens = 500 }
        };

        using var otg = await http.PostAsJsonAsync(
            $"{Adres}?key={klyuch}", zaqvka);

        otg.EnsureSuccessStatusCode();

        using var dok = JsonDocument.Parse(await otg.Content.ReadAsStringAsync());
        return dok.RootElement
                  .GetProperty("candidates")[0]
                  .GetProperty("content")
                  .GetProperty("parts")[0]
                  .GetProperty("text")
                  .GetString() ?? "";
    }
}
  • static readonly HttpClient — един за целия живот на приложението.
  • async / await — иначе интерфейсът замръзва, докато моделът отговаря.
  • EnsureSuccessStatusCode хвърля при 4xx и 5xx — оттам се закача обработката от урок 18.

Асинхронност в интерфейса

Извикването отнема секунди. В настолно приложение това означава замразен прозорец, ако се извика синхронно.

private async void btnPitay_Click(object sender, EventArgs e)
{
    btnPitay.Enabled = false;
    lblStatus.Text = "Изчаквам отговор…";
    try
    {
        string otgovor = await klient.PitayAsync(Sistemno, txtVapros.Text);
        txtOtgovor.Text = otgovor;
        await ZapishiRazgovorAsync(txtVapros.Text, otgovor);
    }
    catch (HttpRequestException ex)
    {
        lblStatus.Text = "Услугата не отговори. Опитайте отново.";
        Zhurnal.Zapishi(ex);
    }
    finally
    {
        btnPitay.Enabled = true;
    }
}
  • Бутонът се изключва, за да няма двойни заявки.
  • Показва се състояние — потребителят трябва да вижда, че нещо се случва.
  • Грешката се показва разбираемо, а подробностите отиват в журнала.
  • finally връща интерфейса в изправност независимо от изхода.

Връзка с базата

Записът на разговора се прави със същия ADO.NET, който познавате от модул 4 — параметризирани команди, без слепване на низове.

public async Task ZapishiRazgovorAsync(string vapros, string otgovor)
{
    const string sql = @"
        INSERT INTO sabshtenia (razgovor_id, rolya, tekst, model, versia_prompt)
        VALUES (@r, 'user', @v, NULL, NULL), (@r, 'model', @o, @m, @p);";

    using var vrazka = new SqlConnection(NizNaVrazka);
    await vrazka.OpenAsync();

    using var kom = new SqlCommand(sql, vrazka);
    kom.Parameters.AddWithValue("@r", razgovorId);
    kom.Parameters.AddWithValue("@v", vapros);
    kom.Parameters.AddWithValue("@o", otgovor);
    kom.Parameters.AddWithValue("@m", "gemini-2.5-flash");
    kom.Parameters.AddWithValue("@p", VersiaPrompt);

    await kom.ExecuteNonQueryAsync();
}

Същото правило като за SQL инжекция

Текстът на въпроса идва от потребителя, а отговорът — от модела. И двете са външен вход. Параметризираните команди са задължителни, не препоръка.

Структуриран изход в C#

Когато отговорът трябва да е JSON по схема (урок 25 от 11. клас), в C# той се разчита в клас — с проверка, а не с надежда.

public sealed record Izvlecheno(
    [property: JsonPropertyName("grad")] string Grad,
    [property: JsonPropertyName("plosht")] double Plosht,
    [property: JsonPropertyName("cena")] int Cena);

Izvlecheno? rezultat = JsonSerializer.Deserialize<Izvlecheno>(otgovor);
if (rezultat is null || rezultat.Cena <= 0)
{
    Zhurnal.Zapishi("Невалиден отговор от модела: " + otgovor);
    return null;
}