Blog

  • UMAI Bilisim
  • 04.10.2026
  • Teknoloji, Semantic Kernel, AI, Agent
  • 0 Yorum

Yerel Yapay Zekâ Agent Geliştirmeye Başlangıç - 5

Semantic Kernel ile Etkileşimli LLM Playground Oluşturmak

Önceki bölümlerde Semantic Kernel ile çalışan ürün asistanımızı MVC tabanlı bir web arayüzüne taşıdık. Kullanıcı artık tarayıcıdan soru sorabiliyor, ProductsPlugin veritabanındaki ürünleri sorgulayabiliyor ve konuşma geçmişi PostgreSQL'de saklanıyor.

Bu bölümde chat ekranını küçük bir LLM laboratuvarına dönüştüreceğiz. Kullanıcı yalnızca soru sormayacak; modelin üretim ayarlarını değiştirerek aynı soruya verilen cevapların nasıl farklılaştığını da gözlemleyebilecek.

Ekrana şu seçenekleri ekleyeceğiz:

  • Temperature
  • Top P
  • Top K
  • Maksimum cevap token sayısı
  • Dengeli, yaratıcı ve odaklı hazır profiller
  • Ollama'da yüklü modeller arasından seçim
  • Streaming cevap
  • Cevap beklenirken animasyon
  • Cevap balonunda kullanılan ayarların gösterilmesi

Bu bölümün amacı yeni bir ürün özelliği eklemekten çok, LLM parametrelerinin çıktıyı nasıl etkilediğini anlaşılır ve denenebilir hale getirmektir.


Neden Sadece Streaming Yetmiyor?

Bir cevabın parça parça ekrana gelmesi kullanıcı deneyimini iyileştirir. Ancak streaming, modelin hangi kelimeleri seçeceğini açıklamaz; yalnızca üretilen metnin aktarılma biçimini değiştirir.

Bir kullanıcı şu iki soruyu ayırt edemeyebilir:

Model daha yaratıcı cevap verdi.
Model cevabı daha erken göstermeye başladı.

Bu nedenle arayüze model üretim ayarlarını da koyuyoruz. Böylece kullanıcı aynı soruyu farklı profillerle gönderip sonucu karşılaştırabiliyor.

Örnek:

Soru: UMAI Bilişim için kısa bir ürün tanıtımı yaz.

Temperature: 0.2
Sonuç: Daha tutarlı ve kontrollü bir cevap

Temperature: 1.2
Sonuç: Daha değişken ve yaratıcı bir cevap

Bu fark her modelde aynı şiddette görülmeyebilir. Modelin büyüklüğü, eğitimi, quantization seviyesi ve kullanılan connector da sonucu etkiler. Ama ayarların gerçekten modele gönderilmesi, deneyi anlamlı hale getirir.


Hangi Ayarları Açıyoruz?

Bu projede yerel Ollama connector'ının doğrudan desteklediği dört ayarı kullanıyoruz.

Temperature

Temperature modelin olası token'lar arasında ne kadar farklı seçim yapmasına izin verileceğini etkiler. Düşük değerler daha kontrollü, yüksek değerler daha değişken cevaplar üretme eğilimindedir.

Bu projedeki aralık: 0.0 - 1.5

Top P

Top P, modelin seçim yapacağı olası token kümesini olasılık dağılımına göre sınırlar. Daha düşük değerler daha dar ve odaklı bir seçim kümesi oluşturur.

Bu projedeki aralık: 0.1 - 1.0

Top K

Top K, modelin sonraki token için değerlendireceği en olası adayların sayısını belirler. Daha düşük değerler daha muhafazakâr, yüksek değerler daha çeşitli seçimler oluşturabilir.

Bu projedeki aralık: 1 - 100

NumPredict

Ollama connector'ındaki NumPredict, üretilecek maksimum çıktı token sayısını belirler. Bu bir kalite ayarı değil, aynı zamanda cevabın üst sınırıdır.

Bu projedeki aralık: 64 - 2048


Thinking Neden Eklenmedi?

Bazı modeller “thinking” veya benzer akıl yürütme seçenekleri sunabilir. Ancak bu ayar tüm LLM sağlayıcılarının ortak bir Semantic Kernel ayarı değildir. Kullanılan model, Ollama sürümü ve connector sürümüne göre adı ve davranışı değişebilir.

Bu nedenle mevcut uygulamaya işlevsiz bir Thinking checkbox'ı koymadık. Bir ayar arayüzde görünüyorsa gerçekten modele gönderilmesi gerekir. İleride belirli bir model ve Ollama API sözleşmesi için model-özel ayarlar eklenebilir.


Ayar Modelini Tanımlamak

Üretim ayarlarını controller parametrelerine dağınık halde bırakmak yerine tek bir record içinde topluyoruz:

public sealed record ChatGenerationOptions(
    double Temperature = 0.7,
    double TopP = 0.9,
    int TopK = 40,
    int MaxTokens = 512)
{
    public ChatGenerationOptions Normalize() => new(
        Math.Clamp(Temperature, 0, 1.5),
        Math.Clamp(TopP, 0.1, 1),
        Math.Clamp(TopK, 1, 100),
        Math.Clamp(MaxTokens, 64, 2048));
}

Buradaki Normalize metodu yalnızca UI doğrulamasına güvenmememizi sağlar. İstemci doğrudan HTTP isteği gönderse bile sunucu kabul edilebilir sınırlar içinde çalışır.


Semantic Kernel Ayarlarına Dönüştürmek

ChatService, uygulamaya ait seçenekleri Ollama connector'ının beklediği OllamaPromptExecutionSettings nesnesine çevirir:

private static OllamaPromptExecutionSettings CreateExecutionSettings(
    ChatGenerationOptions? options)
{
    var normalized = (options ?? new ChatGenerationOptions()).Normalize();

    return new OllamaPromptExecutionSettings
    {
        Temperature = (float)normalized.Temperature,
        TopP = (float)normalized.TopP,
        TopK = normalized.TopK,
        NumPredict = normalized.MaxTokens,
        FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
    };
}

Burada önemli nokta, ayarların yalnızca ekranda gösterilmemesidir. Değerler gerçekten Semantic Kernel çağrısına girer:

await _completionService.GetStreamingChatMessageContentsAsync(
    history,
    kernel: _kernel,
    executionSettings: CreateExecutionSettings(options),
    cancellationToken: cancellationToken);

FunctionChoiceBehavior.Auto() ayarı korunur. Yani model, ürün sorusu için gerekli gördüğünde ProductsPlugin fonksiyonlarını çağırmaya devam eder.


Streaming ile Birlikte Kullanmak

Streaming metodunun artık seçenekleri de alması yeterlidir:

public async IAsyncEnumerable<string> StreamAsync(
    Guid sessionId,
    string question,
    ChatGenerationOptions? options = null,
    [EnumeratorCancellation] CancellationToken cancellationToken = default)

Modelden gelen parçalar tarayıcıya aktarılırken cevap yine StringBuilder içinde biriktirilir. Böylece kullanıcı cevabı anlık görür, tamamlanan cevap da PostgreSQL'e tek assistant mesajı olarak kaydedilir.


Controller ve Arayüz

Form değerleri action parametrelerine bind ediliyor:

public async Task Stream(
    Guid sessionId,
    string question,
    double temperature = 0.7,
    double topP = 0.9,
    int topK = 40,
    int maxTokens = 512,
    CancellationToken cancellationToken = default)

Ardından tek bir ayar nesnesi oluşturup servise iletiyoruz:

var options = new ChatGenerationOptions(
    temperature, topP, topK, maxTokens);

await foreach (var chunk in _chatService.StreamAsync(
    sessionId, question, options, cancellationToken))
{
    await Response.WriteAsync(chunk, cancellationToken);
    await Response.Body.FlushAsync(cancellationToken);
}

Razor ekranında her ayar için range input kullanıyoruz:

<label>Temperature <output id="temperature-value">0.7</output>
    <input id="temperature" name="temperature"
           type="range" min="0" max="1.5" step="0.1" value="0.7" />
</label>

name değeri controller parametresiyle aynı olduğu için FormData bu değeri otomatik olarak POST gövdesine ekler.

Kullanıcıya üç hazır profil de sunuyoruz:

const profiles = {
    balanced: { temperature: "0.7", topP: "0.9", topK: "40", maxTokens: "512" },
    creative: { temperature: "1.2", topP: "0.95", topK: "80", maxTokens: "768" },
    focused: { temperature: "0.2", topP: "0.6", topK: "20", maxTokens: "384" }
};

Hazır profiller bilimsel olarak “doğru” ayarlar değildir. Öğretici kullanım için başlangıç noktalarıdır. Kullanıcı daha sonra slider'ları tek tek değiştirerek kendi deneyini yapabilir.


Ollama Modellerini Listelemek

Sabit bir model adını arayüzde yazmak yerine Ollama'nın model listesini /api/tags endpoint'inden alıyoruz. Bu endpoint, makinenin Ollama üzerinde yüklü modellerini döndürür.

public async Task<IReadOnlyList<string>> GetModelsAsync(
    CancellationToken cancellationToken = default)
{
    var result = await _httpClient.GetFromJsonAsync<OllamaTagsResponse>(
        "/api/tags", cancellationToken);

    return result?.Models
        .Select(model => model.Name)
        .Where(name => !string.IsNullOrWhiteSpace(name))
        .Distinct(StringComparer.OrdinalIgnoreCase)
        .OrderBy(name => name)
        .ToArray() ?? [];
}

Controller bu listeyi ViewModel'e koyar. Razor tarafında kullanıcıya bir select kontrolü sunulur:

<select id="model" name="model" form="chat-form">
    @foreach (var availableModel in Model.Models)
    {
        <option value="@availableModel">@availableModel</option>
    }
</select>

Model adı sadece UI'dan alınarak kullanılmaz. Sunucu, seçilen modelin Ollama listesinde gerçekten bulunduğunu tekrar kontrol eder:

var model = await _modelCatalog.ResolveModelAsync(
    options?.Model, cancellationToken);

Bu kontrol yanlış veya yüklenmemiş bir model adıyla çağrı yapılmasını önler.

Seçilen model daha sonra dinamik Ollama chat completion service'e aktarılır:

return new OllamaChatCompletionService(
    model, new Uri(endpoint), _loggerFactory);

Bu nedenle model seçimi yalnızca ekranda değişen bir etiket değildir; gerçek model çağrısı seçilen model ile yapılır.

Bu örnekte model seçimi Local Ollama provider'ına aittir. OpenAI veya başka bir provider için aynı UI doğrudan kullanılamaz; o provider'ın model listeleme API'si ayrıca uygulanmalıdır.


Cevap Beklerken Animasyon Göstermek

Streaming cevabının ilk parçası gelene kadar kullanıcıya isteğin işlendiğini göstermek için üç noktalı bir animasyon ekliyoruz:

<div class="typing-indicator" aria-label="Cevap hazırlanıyor">
    <i></i><i></i><i></i>
</div>

CSS ile noktaların sırayla hareket etmesini sağlıyoruz:

.typing-indicator i {
    animation: typing 1s infinite ease-in-out;
}

İlk streaming parçası geldiğinde animasyonu gizliyoruz:

if (!done) {
    typingIndicator.hidden = true;
    answerElement.textContent += decoder.decode(value, { stream: true });
}

İstek hata verirse animasyon yine kapatılır ve kullanıcıya bağlantı mesajı gösterilir. Böylece butonun cevap gelene kadar kilitli olmasıyla birlikte kullanıcı hangi aşamada olduğumuzu anlayabilir.


Cevapta Kullanılan Ayarları Göstermek

Streaming cevabı oluştururken arayüzde küçük bir bilgi satırı ekliyoruz:

settings.textContent = `Ayarlar: sıcaklık ${temperature.value}, ` +
    `Top P ${topP.value}, Top K ${topK.value}, ` +
    `maksimum ${maxTokens.value} token`;

Bu bilgi veritabanına assistant mesajının parçası olarak kaydedilmiyor. Sadece o anki deneyin okunabilir olması için arayüzde gösteriliyor.


Aynı Soruyu Tekrar Denemek

Playground'un en faydalı kullanım şekli aynı soruyu değiştirmeden tekrar göndermektir:

Soru: En ucuz ürününüzü kısa ve ikna edici biçimde tanıt.

1. Odaklı profil
2. Yaratıcı profil
3. Temperature 0.7, Top P 0.9, Top K 40

Ürün fiyatı ve stok gibi veritabanından gelen bilgilerde yalnızca yaratıcılığı artırmak her zaman istenen bir şey değildir. Bu tür sorular için düşük temperature ve kontrollü bir profil daha uygun olabilir.

Tanıtım metni gibi yaratıcı görevlerde ise daha yüksek temperature farklı üsluplar görmemizi sağlayabilir.


Tool Calling ile Deney Yaparken Dikkat

Bu uygulamada model iki farklı şey yapabilir:

  1. Doğrudan doğal dil cevabı üretebilir.
  2. Ürün bilgisi gerekiyorsa ProductsPlugin fonksiyonunu çağırabilir.

Temperature gibi ayarlar özellikle doğal dil üretiminde daha görünür hale gelir. Ancak ürün fiyatı veya stok sonucu bir plugin'den geldiğinde modelin bu gerçek veriyi değiştirmemesi gerekir.

Playground ayarları bir model deneyi sunar; veritabanından gelen gerçek değerleri rastgeleleştirmez.


Thinking ve Model-Özel Ayarlar İçin Gelecek Adım

Thinking desteği eklemek istersek önce modelin ve Ollama API'sinin desteklediği alanı doğrulamak gerekir. İleride ayarları provider'a göre ayırabiliriz:

Ortak ayarlar       : temperature, top_p, max_tokens
Ollama ayarları     : top_k, model-specific options
OpenAI ayarları     : provider-specific options
Thinking ayarları   : yalnızca destekleyen modeller

Bu ayrım, çoklu provider desteğini korumak açısından önemlidir.


Test Senaryoları

  1. Dengeli profille bir ürün sorusu sormak.
  2. Aynı soruyu yaratıcı profille tekrar sormak.
  3. Aynı soruyu odaklı profille tekrar sormak.
  4. Cevap balonunda kullanılan ayarların görünmesini kontrol etmek.
  5. Uzun cevapta streaming aktarımını gözlemlemek.
  6. Maksimum token değerini düşürerek cevabın kısaldığını görmek.
  7. Sayfa yenilendiğinde tamamlanmış cevabın geçmişte tek mesaj olarak durduğunu doğrulamak.

Derleme kontrolü:

dotnet build MSSemantic.slnx --no-restore

Bu komut C# ve Razor kodunun derlendiğini gösterir. Model parametrelerinin gerçek çıktıya etkisi için Ollama, PostgreSQL ve tarayıcı üzerinden ayrıca canlı test yapılması gerekir.


Sonuç

Bu bölümde MVC chat ekranını etkileşimli bir LLM Playground'a dönüştürdük:

  • UI ayarları controller üzerinden servise taşındı.
  • ChatGenerationOptions değerleri güvenli aralıklara çekti.
  • Ollama connector'ına Temperature, TopP, TopK ve NumPredict gönderildi.
  • Streaming cevap akışı korundu.
  • Hazır profillerle aynı soru farklı davranışlarla denenebilir hale geldi.
  • Kullanılan ayarlar cevap balonunda görünür oldu.
  • Veritabanı şeması ve migration yapısı değiştirilmedi.

Artık uygulama yalnızca çalışan bir chat örneği değil; Semantic Kernel ile model parametrelerini gözlemlemek için kullanılabilecek küçük bir eğitim laboratuvarıdır.

Github: https://github.com/xkursatx/MSSemantic.git

Yorum Bırak

Yorumunuz admin onayından sonra yayınlanacaktır.

Güvenlik sorusu